Compare commits

..

137 Commits

Author SHA1 Message Date
Philipp Crocoll
a3d5273285 Merge branch 'master' into feature/82-smb-support 2025-07-15 13:45:01 +02:00
PhilippC
cd323c0a22 Merge pull request #2947 from PhilippC/feature/webdav-improvements
Webdav improvements:
 * Add support for chunked uploads (enabled by default)
 * Fix determination of resource type (file vs folder)
 * add support for transactional write
2025-07-15 13:38:08 +02:00
PhilippC
99ca8bf953 Merge pull request #2829 from PhilippC/1617-use-gnu-tls-stream
Use GnuTLS stream for FTPS
2025-07-15 13:23:05 +02:00
Philipp Crocoll
d40b3dc15c fix build issue with NoNet 2025-07-15 13:16:44 +02:00
Philipp Crocoll
057a7e2f7a another nonet fix 2025-07-15 13:12:18 +02:00
PhilippC
1b73c536d5 Merge pull request #2939 from PhilippC/l10n_master3
New Crowdin updates
2025-07-15 12:32:08 +02:00
Philipp Crocoll
2593a8548f Merge branch 'master' into 1617-use-gnu-tls-stream 2025-07-15 12:25:01 +02:00
Philipp Crocoll
13306a9076 fix another build issue in NoNet 2025-07-15 12:20:25 +02:00
Philipp Crocoll
cfb5098b38 add support for transactional upload 2025-07-15 12:18:45 +02:00
Philipp Crocoll
d04d455fbd add missing changelog for 1.13 2025-07-15 12:00:05 +02:00
Philipp Crocoll
b83c4b3772 improve Samba dialog. fix NoNet build 2025-07-15 11:51:15 +02:00
Philipp Crocoll
f03c11381e Merge branch 'master' into feature/82-smb-support 2025-07-15 11:28:49 +02:00
Philipp Crocoll
8a03ddb7f3 always upload files to Github release 2025-07-15 11:08:26 +02:00
Philipp Crocoll
913222d7cb allow chunked uploads, closes https://github.com/PhilippC/keepass2android/issues/2777 2025-07-15 11:07:40 +02:00
Philipp Crocoll
3e6d86c206 correctly check if an item is a folder or file. closes https://github.com/PhilippC/keepass2android/issues/2589 2025-07-15 09:10:41 +02:00
Philipp Crocoll
d6ce2a32e9 allow manually triggering a release workflow run 2025-07-15 08:30:17 +02:00
Philipp Crocoll
21f1c8404c update workflows to not create signed apks during build but only in release. Create releases as drafts. 2025-07-15 08:14:52 +02:00
PhilippC
16ff81cf81 New translations strings.xml (Czech) 2025-07-14 10:57:07 +02:00
PhilippC
0636f687ac New translations strings.xml (Greek) 2025-07-09 13:58:02 +02:00
PhilippC
60d8900473 New translations strings.xml (Greek) 2025-07-09 11:40:00 +02:00
PhilippC
4b2d2ef768 New translations strings.xml (Portuguese, Brazilian) 2025-07-09 02:47:52 +02:00
PhilippC
48899ba9a0 New translations strings.xml (Portuguese, Brazilian) 2025-07-09 01:34:39 +02:00
Philipp Crocoll
092b8689b8 fix build-artifact paths 2025-07-08 21:35:51 +02:00
Philipp Crocoll
3d3ba45cb1 extract keystore for subsequent build step 2025-07-08 18:23:02 +02:00
Philipp Crocoll
b380100307 Manifest and changelog for v1.13 2025-07-08 17:57:52 +02:00
Philipp Crocoll
4c5ddd59d8 build signed apk on every build of master branch 2025-07-08 17:57:37 +02:00
PhilippC
5ed183f318 Merge pull request #2929 from PhilippC/l10n_master3
New Crowdin updates
2025-07-08 17:56:50 +02:00
PhilippC
9c27fd3e78 Merge pull request #2938 from PhilippC/security/audit_suggestions
Security suggestions from Audit
2025-07-08 17:52:16 +02:00
Philipp Crocoll
62c361feb0 revert unintentional change 2025-07-08 17:03:32 +02:00
Philipp Crocoll
0ee2495528 disable password-based QuickUnlock when device is not protected by screen lock 2025-07-08 16:54:45 +02:00
Philipp Crocoll
7dc635a625 Update KeePass2 code for password quality estimation; add and use list of most popular passwords to account for NIST recommendation of using "blocklists" 2025-07-08 12:09:59 +02:00
Philipp Crocoll
e15112c3b4 disable cleartextTrafficPermitted. default to https for links. 2025-07-08 10:35:50 +02:00
PhilippC
1d85fffb18 New translations strings.xml (Chinese Simplified) 2025-07-07 18:44:26 +02:00
PhilippC
5e2f29e737 New translations strings.xml (Chinese Simplified) 2025-07-07 17:20:28 +02:00
Philipp Crocoll
89a09ea142 set version to 1.12-r9d 2025-07-05 14:17:07 +02:00
Philipp Crocoll
628c0d2c19 enable creation of a release 2025-07-05 14:16:17 +02:00
Philipp Crocoll
584feabe44 build process: add previously missing change; fix error in build.yml, more verbose output in release.yml 2025-07-05 13:34:01 +02:00
Philipp Crocoll
ae2cfde897 change makefile to no longer use msbuild but always dotnet 2025-07-05 13:10:21 +02:00
Philipp Crocoll
42c66670b8 release process: make find calls to run in bash 2025-07-05 12:12:49 +02:00
Philipp Crocoll
288539b902 make output more verbose 2025-07-05 11:09:55 +02:00
Philipp Crocoll
61fd32f121 avoid building the "full" apk when calling apk_split 2025-07-05 09:39:54 +02:00
Philipp Crocoll
43108ec4a6 remove ls step in release 2025-07-05 08:35:58 +02:00
Philipp Crocoll
51089c6b98 change build artifact release paths. make archive name depend on matrix variables. 2025-07-05 08:16:30 +02:00
Philipp Crocoll
cf18fcf91c remove distclean from build 2025-07-05 08:15:58 +02:00
Philipp Crocoll
da0513c768 don't create release but only list files to test the release workflow without polluting github releases 2025-07-05 07:49:39 +02:00
Philipp Crocoll
37f520cdbe manifest for r9c 2025-07-05 07:40:21 +02:00
Philipp Crocoll
c98572bee0 fix wildcard for selecting files to upload to release 2025-07-05 07:39:44 +02:00
Philipp Crocoll
b1774ffc4b increase version number ot r9b 2025-07-05 07:19:26 +02:00
Philipp Crocoll
57aaa0c4cd start release action by pushing version tags instead of when release is created on Github 2025-07-05 07:18:21 +02:00
Philipp Crocoll
b961ae1b33 make sure AndroidManifest.xml exists before running nuget 2025-07-05 07:12:17 +02:00
Philipp Crocoll
5e418e2b1b run workload update before any dotnet/nuget calls in github actions 2025-07-05 06:57:00 +02:00
Philipp Crocoll
6d22a213f3 add build target apk_split; build all combinations in a Github action when creating the release 2025-07-05 06:44:28 +02:00
Philipp Crocoll
a76addc43f manifest for 1.12-r9 2025-07-05 06:43:39 +02:00
Philipp Crocoll
1d96217713 remove GooglePlayServices Auth and Base from apk in NoNet build 2025-07-05 06:42:42 +02:00
Philipp Crocoll
d2b8fdcfff allow using Dropbox secrets from environment (better suited for Github action builds) 2025-07-05 06:34:23 +02:00
PhilippC
426fbc2510 New translations strings.xml (Polish) 2025-07-04 23:10:33 +02:00
PhilippC
e89a961c02 New translations strings.xml (Polish) 2025-07-04 22:15:11 +02:00
PhilippC
766c29b7a9 New translations strings.xml (Spanish) 2025-07-03 14:39:21 +02:00
Philipp Crocoll
507b671448 exclude Mega and OneDrive from NoNet build. Manifest for 1.12-r8c 2025-07-02 21:39:37 +02:00
Philipp Crocoll
3118ffaeb5 update version code and name => r8b (to distinguish from previously incorrect r8 build) 2025-07-02 21:04:22 +02:00
Philipp Crocoll
0abe29bd77 restore NoNet build, including removal of MLKit for that Flavor as it adds internet permission 2025-07-02 21:03:20 +02:00
Philipp Crocoll
f3a7831390 upload release artifacts before rebuilding 2025-06-30 22:29:31 +02:00
Philipp Crocoll
37cd58f7ba Merge branch 'v1.12' 2025-06-30 21:59:46 +02:00
Philipp Crocoll
7dd80a8ef7 remove "net" where no longer appropriate from workflow file 2025-06-30 16:16:55 +02:00
Philipp Crocoll
c78636264b align net and nonet manifests 2025-06-30 16:05:02 +02:00
Philipp Crocoll
035506a5a3 add nonet to release workflow 2025-06-30 16:04:38 +02:00
PhilippC
4cf46ef062 Merge pull request #2933 from PhilippC/v1.12
several updates for V1.12
2025-06-30 15:48:57 +02:00
Philipp Crocoll
c7b8063171 add previously forgotten changes 2025-06-30 14:36:59 +02:00
Philipp Crocoll
0a8b149c9a fix failing AutofillTest by adjusting to changed policy 2025-06-30 14:05:26 +02:00
Philipp Crocoll
9240a27791 run workload update before running tests, otherwise dotnet test can fail 2025-06-30 13:38:54 +02:00
PhilippC
e90d5b903c Merge pull request #2932 from PhilippC/2915-bug-exporting-the-key-file-broken-android-file-browser-doesnt-open
Fix crash when exporting key file
2025-06-30 12:43:08 +02:00
Philipp Crocoll
50b4a9f1b9 add missing Theme attributes 2025-06-30 12:42:05 +02:00
Philipp Crocoll
9783c3b5fe manifest for 1.12-r7 2025-06-24 17:12:22 +02:00
Philipp Crocoll
7a837e3237 show autofill more often without requiring manual requests, might mitigate issues in https://github.com/PhilippC/keepass2android/issues/2898 2025-06-24 17:10:50 +02:00
Philipp Crocoll
c8f6714373 refactoring: simplify autofill code 2025-06-24 17:10:08 +02:00
Philipp Crocoll
bc0313aa6a remove no-longer-needed step in release workflow 2025-06-24 15:41:46 +02:00
Philipp Crocoll
0f98668bcd improve app stability and refactor to get better logs 2025-06-24 15:41:11 +02:00
PhilippC
c4206e58bf New translations strings.xml (German) 2025-06-24 15:34:08 +02:00
PhilippC
630ededf3b New translations strings.xml (German) 2025-06-24 13:51:08 +02:00
Philipp Crocoll
8d1195ac96 manifest for v1.12-r6b. Same as v1.12-r6, but need a new version code for Google Play release based on github-action built apk 2025-06-18 03:00:52 +02:00
Philipp Crocoll
df731ac1b3 add checksum to releases; cleanup workflow 2025-06-18 02:40:03 +02:00
Philipp Crocoll
bd784fa13d fix file path for apk to upload 2025-06-18 02:29:47 +02:00
Philipp Crocoll
a6bc5e657c remove no longer needed installation of sdk 26 2025-06-18 02:09:36 +02:00
Philipp Crocoll
56c4cdb321 add Configuration=Release 2025-06-18 02:04:52 +02:00
Philipp Crocoll
42a4a83c7d add upload apk to release in workflow 2025-06-17 22:20:56 +02:00
Philipp Crocoll
fe3933e154 try escape keystore env for building apk 2025-06-17 22:02:21 +02:00
Philipp Crocoll
3efe130ee8 remove unescaped test 2025-06-17 21:59:46 +02:00
Philipp Crocoll
5808857749 fix quoting for shell 2025-06-17 21:12:51 +02:00
Philipp Crocoll
a7397c3316 explicitly set shell=bash 2025-06-17 21:08:13 +02:00
Philipp Crocoll
d12f936898 next attempt 2025-06-17 20:51:10 +02:00
Philipp Crocoll
67f7d74bb9 and more debugging 2025-06-17 20:44:34 +02:00
Philipp Crocoll
b0d0f06073 more debug 2025-06-17 20:37:24 +02:00
Philipp Crocoll
67ee571c27 fix debugging signing 2025-06-17 20:32:15 +02:00
Philipp Crocoll
a360695271 debug signing 2025-06-17 20:28:05 +02:00
Philipp Crocoll
149857a516 make keystore path absolute 2025-06-17 18:45:17 +02:00
Philipp Crocoll
fb8ffb802f try to fix release workflow 2025-06-17 17:50:10 +02:00
Philipp Crocoll
e957073457 add workflow file for generating the "net" release apk 2025-06-17 17:30:16 +02:00
Philipp Crocoll
da86b0f50b manifest for 1.12-r6 2025-06-17 16:55:47 +02:00
PhilippC
0aa78ffd66 Merge pull request #2862 from Gian-Fr/fix-makefile
Fixed MakeFile for Linux
2025-06-17 16:52:04 +02:00
PhilippC
ce0087af99 Merge pull request #2850 from PhilippC/l10n_master3
New Crowdin updates
2025-06-17 16:46:16 +02:00
PhilippC
576bfeecfe Merge pull request #2918 from PhilippC/2869-fix-bad-behavior-when-starting-passwordactivity-twice
Fix bad behavior when starting passwordactivity twice
2025-06-17 16:45:12 +02:00
Philipp Crocoll
c0e2f34b79 use correct message severity when synchronizing 2025-06-17 16:42:30 +02:00
Philipp Crocoll
84d0c32610 fix issue with loading Yubikey aux file. closes #2880 2025-06-17 16:27:43 +02:00
Philipp Crocoll
40184dbd55 catch exception while sending log data. might throw if too much data is sent. 2025-06-17 14:58:06 +02:00
Philipp Crocoll
2d17bdde19 add more logging while loading database to get more info regarding #2868 2025-06-17 14:58:01 +02:00
Philipp Crocoll
e2babde1fa do not reset activity state in OnNewIntent if the activity hasn't even been initialized. closes #2869, closes #2888 2025-06-17 14:04:15 +02:00
Philipp Crocoll
e1f26fb045 cleanup and improve formatting 2025-06-17 13:58:29 +02:00
Philipp Crocoll
adbbfa0ac1 add more logging to diagnose #2891 2025-06-17 08:27:13 +02:00
PhilippC
37a013135e New translations strings.xml (French) 2025-06-05 23:19:38 +02:00
PhilippC
acc6ea7f85 New translations strings.xml (French) 2025-06-05 22:10:08 +02:00
PhilippC
62f012713a New translations strings.xml (Turkish) 2025-06-03 19:34:43 +02:00
PhilippC
0d726c1789 New translations strings.xml (Turkish) 2025-06-03 17:37:29 +02:00
Philipp Crocoll
f162e868b9 link libargon2.so for x86 and x86_64 to fix #2881 2025-06-03 17:07:17 +02:00
PhilippC
0a1f95653f New translations strings.xml (Spanish) 2025-06-01 16:19:37 +02:00
PhilippC
27451825c6 New translations strings.xml (Italian) 2025-05-29 10:32:52 +02:00
PhilippC
bdd6f1033e New translations strings.xml (Romanian) 2025-05-26 14:25:46 +02:00
PhilippC
c0413f9b74 New translations strings.xml (Turkish) 2025-05-26 12:52:41 +02:00
PhilippC
59d6fc8fdb New translations strings.xml (German) 2025-05-16 08:18:05 +02:00
PhilippC
c500245647 New translations strings.xml (German) 2025-05-16 06:50:12 +02:00
PhilippC
026a263f10 New translations strings.xml (German) 2025-05-13 10:41:52 +02:00
PhilippC
839e6d3cb4 New translations strings.xml (French) 2025-05-12 04:51:42 +02:00
PhilippC
735f4caf89 New translations strings.xml (French) 2025-05-12 03:33:02 +02:00
PhilippC
11af71ef82 New translations strings.xml (Slovak) 2025-05-12 00:26:40 +02:00
PhilippC
e09577d17f New translations strings.xml (Slovak) 2025-05-11 22:34:40 +02:00
Gian-Fr
c1dbf171f5 Fixed MakeFile for Linux 2025-05-01 16:21:02 +02:00
PhilippC
4ca4ec10be New translations strings.xml (Czech) 2025-04-24 11:00:27 +02:00
PhilippC
77fded4964 New translations strings.xml (Chinese Simplified) 2025-04-24 11:00:26 +02:00
PhilippC
578491b1c7 New translations strings.xml (Slovenian) 2025-04-24 08:40:12 +02:00
PhilippC
eee3ffd861 New translations strings.xml (Slovenian) 2025-04-24 07:18:52 +02:00
PhilippC
89696d7f0d New translations strings.xml (Portuguese, Brazilian) 2025-04-23 00:14:27 +02:00
PhilippC
a202c76bf0 New translations strings.xml (Chinese Simplified) 2025-04-22 08:10:50 +02:00
PhilippC
c9936ab76b New translations strings.xml (Italian) 2025-04-16 21:31:25 +02:00
PhilippC
7ac6f7ed51 New translations strings.xml (Italian) 2025-04-16 21:31:24 +02:00
Philipp Crocoll
ba7b02cd1e remove testing credentials 2025-04-08 15:46:38 +02:00
Philipp Crocoll
aec9441de4 update FluentFTP 2025-04-08 15:26:04 +02:00
Philipp Crocoll
5edf42254d this is an experiment to use GnuTlsStream (the ftpcredentials.xml have some hardcoded credentials for a public FTP server for testing). Unfortunately, the app restarts when loading the native libraries for GnuTLS. 2025-04-01 15:10:04 +02:00
Philipp Crocoll
a51bfb102f implements Samba support to close #82 2025-03-05 08:14:02 +01:00
143 changed files with 16754 additions and 6260 deletions

View File

@@ -78,7 +78,7 @@ jobs:
# - name: Build keepass2android (net) # - name: Build keepass2android (net)
# run: | # run: |
# make msbuild Flavor=Net # make dotnetbuild Flavor=Net
# - name: Build APK (net) # - name: Build APK (net)
# run: | # run: |
@@ -96,7 +96,7 @@ jobs:
# - name: Build keepass2android (nonet) # - name: Build keepass2android (nonet)
# run: | # run: |
# make msbuild Flavor=NoNet # make dotnetbuild Flavor=NoNet
# - name: Build APK (nonet) # - name: Build APK (nonet)
# run: | # run: |
@@ -212,7 +212,7 @@ jobs:
# - name: Build keepass2android (net) # - name: Build keepass2android (net)
# run: | # run: |
# make msbuild Flavor=Net # make dotnetbuild Flavor=Net
# - name: Build APK (net) # - name: Build APK (net)
# run: | # run: |
@@ -230,7 +230,7 @@ jobs:
# - name: Build keepass2android (nonet) # - name: Build keepass2android (nonet)
# run: | # run: |
# make msbuild Flavor=NoNet # make dotnetbuild Flavor=NoNet
# - name: Build APK (nonet) # - name: Build APK (nonet)
# run: | # run: |
@@ -279,7 +279,7 @@ jobs:
with: with:
minimum-size: 8GB minimum-size: 8GB
- name: Add msbuild to PATH - name: Add dotnetbuild to PATH
uses: microsoft/setup-msbuild@v2 uses: microsoft/setup-msbuild@v2
# If we want to also have nmake, use this instead # If we want to also have nmake, use this instead
#uses: ilammy/msvc-dev-cmd@v1 #uses: ilammy/msvc-dev-cmd@v1
@@ -309,30 +309,49 @@ jobs:
run: | run: |
make java make java
- name: Update dotnet workloads
run: |
dotnet workload update
- name: Select the manifest
run: |
make manifestlink Flavor=Net
- name: Install NuGet dependencies (net) - name: Install NuGet dependencies (net)
run: make nuget Flavor=Net run: make nuget Flavor=Net
- name: Build keepass2android (net) - name: Build keepass2android (net)
run: | run: |
make msbuild Flavor=Net make dotnetbuild Flavor=Net
- name: Build APK (net) - name: Build APK (net)
if: github.ref == 'refs/heads/master'
env:
DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }}
DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }}
DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }}
DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }}
run: | run: |
make apk Flavor=Net make apk Configuration=Release Flavor=Net
- name: Archive production artifacts (net) - name: Archive production artifacts (net)
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: signed APK ('net' built on ${{ github.job }}) name: archive APK ('net' built on ${{ github.job }})
path: | path: |
src/keepass2android/bin/*/*-Signed.apk src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk
- name: Select the manifest
run: |
make manifestlink Flavor=NoNet
- name: Install NuGet dependencies (nonet) - name: Install NuGet dependencies (nonet)
run: make nuget Flavor=NoNet run: make nuget Flavor=NoNet
- name: Build keepass2android (nonet) - name: Build keepass2android (nonet)
run: | run: |
make msbuild Flavor=NoNet make dotnetbuild Flavor=NoNet
- name: Test Autofill - name: Test Autofill
working-directory: ./src/Kp2aAutofillParser.Tests working-directory: ./src/Kp2aAutofillParser.Tests
run: dotnet test run: dotnet test
@@ -344,9 +363,7 @@ jobs:
- name: Archive production artifacts (nonet) - name: Archive production artifacts (nonet)
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: signed APK ('nonet' built on ${{ github.job }}) name: archive APK ('nonet' built on ${{ github.job }})
path: | path: |
src/keepass2android/bin/*/*-Signed.apk src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk
- name: Perform "make distclean"
run: make distclean

147
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,147 @@
name: Create keepass2android release
env:
NAME: 'Release'
on:
push:
tags:
- "v1.*"
workflow_dispatch: # Allows manual triggering of the workflow
jobs:
build-release:
runs-on: windows-2022
strategy:
matrix:
flavor: [Net, NoNet]
target: [apk, apk_split]
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Extract key store
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KeyStore: "${{ github.workspace }}/kp2a.keystore"
shell: bash
run: |
echo $KeyStore
echo $KEYSTORE_BASE64 | base64 --decode > $KeyStore
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('src/**/*.csproj', 'src/**/packages.config') }}
restore-keys: |
${{ runner.os }}-nuget-
# Workaround an issue when building on windows-2022. Error was
# D8 : OpenJDK 64-Bit Server VM warning : INFO: os::commit_memory(0x00000000ae400000, 330301440, 0) failed; error='The paging file is too small for this operation to complete' (DOS error/errno=1455) [D:\a\keepass2android\keepass2android\src\keepass2android\keepass2android-app.csproj]
# C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.D8.targets(81,5): error MSB6006: "java.exe" exited with code 1. [D:\a\keepass2android\keepass2android\src\keepass2android\keepass2android-app.csproj]
- name: Configure Pagefile
uses: al-cheb/configure-pagefile-action@a3b6ebd6b634da88790d9c58d4b37a7f4a7b8708 # v1.4
with:
minimum-size: 8GB
- name: Add msbuild/dotnet to PATH
uses: microsoft/setup-msbuild@v2
# If we want to also have nmake, use this instead
#uses: ilammy/msvc-dev-cmd@v1
- name: Switch to JDK-17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Display java version
run: java -version
- name: Build native dependencies
shell: cmd
run: |
make native
- name: Build java dependencies
shell: cmd
run: |
make java
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Update dotnet workloads
run: |
dotnet workload update
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Select the manifest
run: |
make manifestlink Flavor=${{ matrix.flavor }}
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Install NuGet dependencies
run: make nuget Flavor=${{ matrix.flavor }}
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Build APK (net)
env:
KeyStore: "${{ github.workspace }}/kp2a.keystore"
MyAndroidSigningStorePass: ${{ secrets.KEY_STORE_PASSWORD }}
MyAndroidSigningKeyPass: ${{ secrets.KEY_PASSWORD }}
DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }}
DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }}
DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }}
DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }}
run: |
make ${{ matrix.target }} Configuration=Release Flavor=${{ matrix.flavor }}
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: keepass2android_${{ matrix.target }}_${{ matrix.flavor }}
# the first line is for "apk" target, the second line is for "apk_split" target
path: |
src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk
src/keepass2android-app/bin/Release/net8.0-android/*/publish/*.apk
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Upload APK to GitHub Release
uses: softprops/action-gh-release@v2
with:
draft: true
files: |
src/keepass2android-app/bin/Release/net8.0-android/publish/*.apk
src/keepass2android-app/bin/Release/net8.0-android/*/publish/*.apk

2
.gitignore vendored
View File

@@ -64,7 +64,7 @@ Thumbs.db
/src/java/android-filechooser/code/projectzip/project.zip /src/java/android-filechooser/code/projectzip/project.zip
/src/java/android-filechooser/code/unused.txt /src/java/android-filechooser/code/unused.txt
/src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs /src/Kp2aBusinessLogic/Io/DropboxFileStorage.g.cs
/src/java/workspace/DriveTest /src/java/workspace/DriveTest

View File

@@ -4,10 +4,10 @@
# This Makefile can be used on both unix-like (use make) & windows (with GNU make) # This Makefile can be used on both unix-like (use make) & windows (with GNU make)
# #
# append the Configuration variable to 'make' call with value to use in '/p:Configuration=' # append the Configuration variable to 'make' call with value to use in '/p:Configuration='
# of msbuild command. # of dotnetbuild command.
# #
# append the Flavor variable to 'make' call with value to use in '/p:Flavor=' # append the Flavor variable to 'make' call with value to use in '/p:Flavor='
# of msbuild command. # of dotnetbuild command.
# #
# Example: # Example:
# make Configuration=Release Flavor=NoNet # make Configuration=Release Flavor=NoNet
@@ -18,7 +18,7 @@
# - native: build the native libs # - native: build the native libs
# - java: build the java libs # - java: build the java libs
# - nuget: restore NuGet packages # - nuget: restore NuGet packages
# - msbuild: build the project # - dotnetbuild: build the project
# - apk: same as all # - apk: same as all
# - manifestlink: creates a symlink (to be used in building) to the AndroidManifest corresponding to the selected Flavor # - manifestlink: creates a symlink (to be used in building) to the AndroidManifest corresponding to the selected Flavor
# #
@@ -27,7 +27,7 @@
# - clean_native: clean native lib # - clean_native: clean native lib
# - clean_java: call clean target of java libs # - clean_java: call clean target of java libs
# - clean_nuget: cleanup the 'nuget restore' # - clean_nuget: cleanup the 'nuget restore'
# - clean_msbuild: call clean target of msbuild # - clean_dotnet: call clean target of dotnetbuild
# #
# #
# #
@@ -60,45 +60,23 @@ $(info MAKESHELL: $(MAKESHELL))
$(info SHELL: $(SHELL)) $(info SHELL: $(SHELL))
$(info ) $(info )
# On linux use xabuild, on Windows use MSBuild.exe, otherwise (macos?) use msbuild.
ifeq ($(detected_OS),Linux) ifeq ($(detected_OS),Linux)
MSBUILD_binary := xabuild DOTNET_binary := dotnet
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary)) DOTNET := $(shell $(WHICH) $(DOTNET_binary))
else ifeq ($(detected_OS),Windows) else ifeq ($(detected_OS),Windows)
MSBUILD_binary := MSBuild.exe DOTNET_binary := dotnet
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary) 2> nul) DOTNET := $(shell $(WHICH) $(DOTNET_binary) 2> nul)
ifeq ($(MSBUILD),)
# Additional heuristic to find MSBUILD_BINARY on Windows
VSWHERE := "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
VSWHERE_CHECK := $(shell @echo off & $(VSWHERE) 2> nul || echo VSWHERE_NOT_FOUND)
ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND)
MSBUILD := $(shell @echo off & $(VSWHERE) -latest -prerelease -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe)
VS_INSTALL_PATH := $(shell @echo off & $(VSWHERE) -property installationPath)
endif
endif
else else
MSBUILD_binary := msbuild DOTNET_binary := dotnet
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary)) DOTNET := $(shell $(WHICH) $(DOTNET_binary))
endif endif
ifeq ($(MSBUILD),) ifeq ($(DOTNET),)
$(info ) $(info )
$(info '$(MSBUILD_binary)' binary could not be found. Check it is in your PATH.) $(info '$(DOTNET_binary)' binary could not be found. Check it is in your PATH.)
ifeq ($(detected_OS),Windows)
ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND)
$(info )
$(info You may retry after running in the command prompt:)
$(info )
$(info "$(VS_INSTALL_PATH)\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64)
$(info )
$(info If this doesn't work, install/find the location of vcvarsall.bat)
$(info or install and add msbuild.exe to your PATH)
$(info )
endif
endif
$(error ) $(error )
endif endif
$(info MSBUILD: $(MSBUILD)) $(info DOTNET: $(DOTNET))
$(info ) $(info )
ifeq ($(ANDROID_SDK_ROOT),) ifeq ($(ANDROID_SDK_ROOT),)
@@ -117,7 +95,7 @@ endif
$(info ANDROID_NDK_ROOT: $(ANDROID_NDK_ROOT)) $(info ANDROID_NDK_ROOT: $(ANDROID_NDK_ROOT))
ifneq ($(Configuration),) ifneq ($(Configuration),)
MSBUILD_PARAM = -p:Configuration="$(Configuration)" DOTNET_PARAM = -p:Configuration="$(Configuration)"
else else
$(warning Configuration environment variable not set.) $(warning Configuration environment variable not set.)
endif endif
@@ -127,7 +105,7 @@ CREATE_MANIFEST_LINK :=
MANIFEST_FILE := MANIFEST_FILE :=
ifneq ($(Flavor),) ifneq ($(Flavor),)
MSBUILD_PARAM += -p:Flavor="$(Flavor)" DOTNET_PARAM += -p:Flavor="$(Flavor)"
ifneq ($(Flavor),) ifneq ($(Flavor),)
ifeq ($(Flavor),Debug) ifeq ($(Flavor),Debug)
MANIFEST_FILE := AndroidManifest_debug.xml MANIFEST_FILE := AndroidManifest_debug.xml
@@ -152,7 +130,7 @@ else
endif endif
ifneq ($(KeyStore),) ifneq ($(KeyStore),)
MSBUILD_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a" DOTNET_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a"
endif endif
ifeq ($(detected_OS),Windows) ifeq ($(detected_OS),Windows)
@@ -176,7 +154,7 @@ endif
# Recursive wildcard: https://stackoverflow.com/a/18258352 # Recursive wildcard: https://stackoverflow.com/a/18258352
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
$(info MSBUILD_PARAM: $(MSBUILD_PARAM)) $(info DOTNET_PARAM: $(DOTNET_PARAM))
$(info nuget path: $(shell $(WHICH) nuget)) $(info nuget path: $(shell $(WHICH) nuget))
$(info ) $(info )
@@ -254,7 +232,7 @@ OUTPUT_PluginQR = src/java/Keepass2AndroidPluginSDK2/app/build/outputs/aar/Keepa
.PHONY: native $(NATIVE_COMPONENTS) clean_native $(NATIVE_CLEAN_TARGETS) \ .PHONY: native $(NATIVE_COMPONENTS) clean_native $(NATIVE_CLEAN_TARGETS) \
java $(JAVA_COMPONENTS) clean_java $(JAVA_CLEAN_TARGETS) \ java $(JAVA_COMPONENTS) clean_java $(JAVA_CLEAN_TARGETS) \
nuget clean_nuget \ nuget clean_nuget \
msbuild clean_msbuild \ dotnetbuild clean_dotnet \
apk all clean apk all clean
all: apk all: apk
@@ -303,7 +281,7 @@ ifeq ($(shell $(WHICH) nuget),)
endif endif
$(RMFILE) stamp.nuget_* $(RMFILE) stamp.nuget_*
nuget restore src/KeePass.sln nuget restore src/KeePass.sln
$(MSBUILD) src/KeePass.sln -t:restore $(MSBUILD_PARAM) -p:RestorePackagesConfig=true $(DOTNET) restore src/KeePass.sln $(DOTNET_PARAM) -p:RestorePackagesConfig=true
@echo "" > stamp.nuget_$(Flavor) @echo "" > stamp.nuget_$(Flavor)
manifestlink: manifestlink:
@@ -312,20 +290,21 @@ manifestlink:
$(CREATE_MANIFEST_LINK) $(CREATE_MANIFEST_LINK)
##### #####
src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs:
ifeq ($(detected_OS),Windows)
$(CP) src\Kp2aBusinessLogic\Io\DropboxFileStorageKeysDummy.cs src\Kp2aBusinessLogic\Io\DropboxFileStorageKeys.cs
else
$(CP) src/Kp2aBusinessLogic/Io/DropboxFileStorageKeysDummy.cs $@
endif
msbuild: manifestlink native java nuget src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs dotnetbuild: manifestlink native java nuget
$(MSBUILD) src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(MSBUILD_PARAM) -p:Platform="Any CPU" -m $(DOTNET) build src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(DOTNET_PARAM) -p:Platform="Any CPU" -m
apk: msbuild apk: manifestlink native java nuget
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m $(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m
build_all: msbuild apk_split: manifestlink native java nuget
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm64
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x86
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x64
src/build-scripts/rename-output-apks.sh src/keepass2android-app/bin/Release/net8.0-android/
build_all: dotnetbuild
##### Cleanup targets ##### Cleanup targets
@@ -369,10 +348,10 @@ else
endif endif
$(RMFILE) stamp.nuget_* $(RMFILE) stamp.nuget_*
clean_msbuild: clean_dotnet:
$(MSBUILD) src/KeePass.sln -target:clean $(MSBUILD_PARAM) $(DOTNET) clean src/KeePass.sln $(DOTNET_PARAM)
clean: clean_native clean_java clean_nuget clean_msbuild clean: clean_native clean_java clean_nuget clean_dotnet
distclean: clean distclean: clean
ifneq ("$(wildcard ./allow_git_clean)","") ifneq ("$(wildcard ./allow_git_clean)","")

View File

@@ -11,10 +11,10 @@ Regular stable releases of Keepass2Android are available on [Google Play](https:
Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet). Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet).
# How can I contribute? # How can I contribute?
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://crowdin.net/project/keepass2android) * Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](https://crowdin.net/project/keepass2android)
* Add features by [creating a plugin](How-to-create-a-plug-in_.md) or creating a pull request. You might want to contact me before you start working so I can coordinate efforts. * Add features by [creating a plugin](How-to-create-a-plug-in_.md) or creating a pull request. You might want to contact me before you start working so I can coordinate efforts.
* [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC) * [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC)
* [Make a donation](http://philipp.crocoll.net/donate.php) * [Make a donation](https://philipp.crocoll.net/donate.php)
# How do I learn more? # How do I learn more?
Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information. Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information.

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -29,197 +29,226 @@ using KeePassLib.Utility;
namespace KeePassLib.Cryptography namespace KeePassLib.Cryptography
{ {
/// <summary> /// <summary>
/// Algorithms supported by <c>CryptoRandomStream</c>. /// Algorithms supported by <c>CryptoRandomStream</c>.
/// </summary> /// </summary>
public enum CrsAlgorithm public enum CrsAlgorithm
{ {
/// <summary> /// <summary>
/// Not supported. /// Not supported.
/// </summary> /// </summary>
Null = 0, Null = 0,
/// <summary> /// <summary>
/// A variant of the ARCFour algorithm (RC4 incompatible). /// A variant of the ArcFour algorithm (RC4 incompatible).
/// </summary> /// Insecure; for backward compatibility only.
/// </summary> /// </summary>
ArcFourVariant = 1, ArcFourVariant = 1,
/// <summary> /// <summary>
/// Salsa20 stream cipher algorithm. /// Salsa20 stream cipher algorithm.
/// </summary> /// </summary>
Salsa20 = 2, Salsa20 = 2,
/// <summary> /// <summary>
/// ChaCha20 stream cipher algorithm. /// ChaCha20 stream cipher algorithm.
/// </summary> /// </summary>
ChaCha20 = 3, ChaCha20 = 3,
Count = 4 Count = 4
} }
/// <summary> /// <summary>
/// A random stream class. The class is initialized using random /// A random stream class. The class is initialized using random
/// bytes provided by the caller. The produced stream has random /// bytes provided by the caller. The produced stream has random
/// properties, but for the same seed always the same stream /// properties, but for the same seed always the same stream
/// is produced, i.e. this class can be used as stream cipher. /// is produced, i.e. this class can be used as stream cipher.
/// </summary> /// </summary>
public sealed class CryptoRandomStream : IDisposable public sealed class CryptoRandomStream : IDisposable
{ {
private readonly CrsAlgorithm m_crsAlgorithm; private readonly CrsAlgorithm m_alg;
private bool m_bDisposed = false;
private byte[] m_pbState = null; private readonly byte[] m_pbKey = null;
private byte m_i = 0; private readonly byte[] m_pbIV = null;
private byte m_j = 0;
private Salsa20Cipher m_salsa20 = null; private readonly ChaCha20Cipher m_chacha20 = null;
private ChaCha20Cipher m_chacha20 = null; private readonly Salsa20Cipher m_salsa20 = null;
/// <summary> private readonly byte[] m_pbState = null;
/// Construct a new cryptographically secure random stream object. private byte m_i = 0;
/// </summary> private byte m_j = 0;
/// <param name="genAlgorithm">Algorithm to use.</param>
/// <param name="pbKey">Initialization key. Must not be <c>null</c> and
/// must contain at least 1 byte.</param>
public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
{
if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
/// <exception cref="System.ArgumentNullException">Thrown if the
int cbKey = pbKey.Length;
if(cbKey <= 0)
{
Debug.Assert(false); // Need at least one byte
throw new ArgumentOutOfRangeException("pbKey");
}
/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception>
m_crsAlgorithm = a;
/// <exception cref="System.ArgumentException">Thrown if the
if(a == CrsAlgorithm.ChaCha20)
{
byte[] pbKey32 = new byte[32];
byte[] pbIV12 = new byte[12];
/// <paramref name="pbKey" /> parameter contains no bytes or the
using(SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbKey);
Array.Copy(pbHash, pbKey32, 32);
Array.Copy(pbHash, 32, pbIV12, 0, 12);
MemUtil.ZeroByteArray(pbHash);
}
/// algorithm is unknown.</exception>
m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true);
}
else if(a == CrsAlgorithm.Salsa20)
{
byte[] pbKey32 = CryptoUtil.HashSha256(pbKey);
byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
0x97, 0x20, 0x5D, 0x2A }; // Unique constant
m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); /// <summary>
} /// Construct a new cryptographically secure random stream object.
else if(a == CrsAlgorithm.ArcFourVariant) /// </summary>
{ /// <param name="a">Algorithm to use.</param>
// Fill the state linearly /// <param name="pbKey">Initialization key. Must not be <c>null</c>
m_pbState = new byte[256]; /// and must contain at least 1 byte.</param>
for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
{
if (pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
unchecked int cbKey = pbKey.Length;
{ if (cbKey <= 0)
byte j = 0, t; {
int inxKey = 0; Debug.Assert(false); // Need at least one byte
for(int w = 0; w < 256; ++w) // Key setup throw new ArgumentOutOfRangeException("pbKey");
{ }
j += (byte)(m_pbState[w] + pbKey[inxKey]);
t = m_pbState[0]; // Swap entries m_alg = a;
m_pbState[0] = m_pbState[j];
m_pbState[j] = t;
++inxKey; if (a == CrsAlgorithm.ChaCha20)
if(inxKey >= cbKey) inxKey = 0; {
} m_pbKey = new byte[32];
} m_pbIV = new byte[12];
GetRandomBytes(512); // Increases security, see cryptanalysis using (SHA512Managed h = new SHA512Managed())
} {
else // Unknown algorithm byte[] pbHash = h.ComputeHash(pbKey);
{ Array.Copy(pbHash, m_pbKey, 32);
Debug.Assert(false); Array.Copy(pbHash, 32, m_pbIV, 0, 12);
throw new ArgumentOutOfRangeException("a"); MemUtil.ZeroByteArray(pbHash);
} }
}
public void Dispose() m_chacha20 = new ChaCha20Cipher(m_pbKey, m_pbIV, true);
{ }
Dispose(true); else if (a == CrsAlgorithm.Salsa20)
GC.SuppressFinalize(this); {
} m_pbKey = CryptoUtil.HashSha256(pbKey);
m_pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
0x97, 0x20, 0x5D, 0x2A }; // Unique constant
private void Dispose(bool disposing) m_salsa20 = new Salsa20Cipher(m_pbKey, m_pbIV);
{ }
if(disposing) else if (a == CrsAlgorithm.ArcFourVariant)
{ {
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) // Fill the state linearly
m_chacha20.Dispose(); m_pbState = new byte[256];
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) for (int w = 0; w < 256; ++w) m_pbState[w] = (byte)w;
m_salsa20.Dispose();
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant)
{
MemUtil.ZeroByteArray(m_pbState);
m_i = 0;
m_j = 0;
}
else { Debug.Assert(false); }
}
}
/// <summary> unchecked
/// Get <paramref name="uRequestedCount" /> random bytes. {
/// </summary> byte j = 0, t;
/// <param name="uRequestedCount">Number of random bytes to retrieve.</param> int inxKey = 0;
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns> for (int w = 0; w < 256; ++w) // Key setup
public byte[] GetRandomBytes(uint uRequestedCount) {
{ j += (byte)(m_pbState[w] + pbKey[inxKey]);
if(uRequestedCount == 0) return MemUtil.EmptyByteArray;
if(uRequestedCount > (uint)int.MaxValue) t = m_pbState[0]; // Swap entries
throw new ArgumentOutOfRangeException("uRequestedCount"); m_pbState[0] = m_pbState[j];
int cb = (int)uRequestedCount; m_pbState[j] = t;
byte[] pbRet = new byte[cb]; ++inxKey;
if (inxKey >= cbKey) inxKey = 0;
}
}
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) GetRandomBytes(512); // Increases security, see cryptanalysis
m_chacha20.Encrypt(pbRet, 0, cb); }
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) else // Unknown algorithm
m_salsa20.Encrypt(pbRet, 0, cb); {
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) Debug.Assert(false);
{ throw new ArgumentOutOfRangeException("a");
unchecked }
{ }
for(int w = 0; w < cb; ++w)
{
++m_i;
m_j += m_pbState[m_i];
byte t = m_pbState[m_i]; // Swap entries public void Dispose()
m_pbState[m_i] = m_pbState[m_j]; {
m_pbState[m_j] = t; Dispose(true);
GC.SuppressFinalize(this);
}
t = (byte)(m_pbState[m_i] + m_pbState[m_j]); private void Dispose(bool disposing)
pbRet[w] = m_pbState[t]; {
} if (disposing)
} {
} if (m_alg == CrsAlgorithm.ChaCha20)
else { Debug.Assert(false); } m_chacha20.Dispose();
else if (m_alg == CrsAlgorithm.Salsa20)
m_salsa20.Dispose();
else if (m_alg == CrsAlgorithm.ArcFourVariant)
{
MemUtil.ZeroByteArray(m_pbState);
m_i = 0;
m_j = 0;
}
else { Debug.Assert(false); }
return pbRet; if (m_pbKey != null) MemUtil.ZeroByteArray(m_pbKey);
} if (m_pbIV != null) MemUtil.ZeroByteArray(m_pbIV);
public ulong GetRandomUInt64() m_bDisposed = true;
{ }
byte[] pb = GetRandomBytes(8); }
return MemUtil.BytesToUInt64(pb);
} /// <summary>
/// Get <paramref name="uRequestedCount" /> random bytes.
/// </summary>
/// <param name="uRequestedCount">Number of random bytes to retrieve.</param>
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns>
public byte[] GetRandomBytes(uint uRequestedCount)
{
if (m_bDisposed) throw new ObjectDisposedException(null);
if (uRequestedCount == 0) return MemUtil.EmptyByteArray;
if (uRequestedCount > (uint)int.MaxValue)
throw new ArgumentOutOfRangeException("uRequestedCount");
int cb = (int)uRequestedCount;
byte[] pbRet = new byte[cb];
if (m_alg == CrsAlgorithm.ChaCha20)
m_chacha20.Encrypt(pbRet, 0, cb);
else if (m_alg == CrsAlgorithm.Salsa20)
m_salsa20.Encrypt(pbRet, 0, cb);
else if (m_alg == CrsAlgorithm.ArcFourVariant)
{
unchecked
{
for (int w = 0; w < cb; ++w)
{
++m_i;
m_j += m_pbState[m_i];
byte t = m_pbState[m_i]; // Swap entries
m_pbState[m_i] = m_pbState[m_j];
m_pbState[m_j] = t;
t = (byte)(m_pbState[m_i] + m_pbState[m_j]);
pbRet[w] = m_pbState[t];
}
}
}
else { Debug.Assert(false); }
return pbRet;
}
public ulong GetRandomUInt64()
{
byte[] pb = GetRandomBytes(8);
return MemUtil.BytesToUInt64(pb);
}
internal ulong GetRandomUInt64(ulong uMaxExcl)
{
if (uMaxExcl == 0) { Debug.Assert(false); throw new ArgumentOutOfRangeException("uMaxExcl"); }
ulong uGen, uRem;
do
{
uGen = GetRandomUInt64();
uRem = uGen % uMaxExcl;
}
while ((uGen - uRem) > (ulong.MaxValue - (uMaxExcl - 1UL)));
// This ensures that the last number of the block (i.e.
// (uGen - uRem) + (uMaxExcl - 1)) is generatable;
// for signed longs, overflow to negative number:
// while((uGen - uRem) + (uMaxExcl - 1) < 0);
return uRem;
}
#if CRSBENCHMARK #if CRSBENCHMARK
public static string Benchmark() public static string Benchmark()
@@ -237,22 +266,21 @@ namespace KeePassLib.Cryptography
return str; return str;
} }
private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize) private static int BenchTime(CrsAlgorithm a, int nRounds, int cbData)
{ {
byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 }; byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 };
int nStart = Environment.TickCount; int tStart = Environment.TickCount;
for(int i = 0; i < nRounds; ++i) for(int i = 0; i < nRounds; ++i)
{ {
using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey)) using(CryptoRandomStream crs = new CryptoRandomStream(a, pbKey))
{ {
c.GetRandomBytes((uint)nDataSize); crs.GetRandomBytes((uint)cbData);
} }
} }
int nEnd = Environment.TickCount;
return (nEnd - nStart); return (Environment.TickCount - tStart);
} }
#endif #endif
} }
} }

View File

@@ -1,65 +0,0 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator
{
internal static class CharSetBasedGenerator
{
internal static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crsRandomSource)
{
psOut = ProtectedString.Empty;
if(pwProfile.Length == 0) return PwgError.Success;
PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString());
char[] vGenerated = new char[pwProfile.Length];
PwGenerator.PrepareCharSet(pcs, pwProfile);
for(int nIndex = 0; nIndex < (int)pwProfile.Length; ++nIndex)
{
char ch = PwGenerator.GenerateCharacter(pwProfile, pcs,
crsRandomSource);
if(ch == char.MinValue)
{
MemUtil.ZeroArray<char>(vGenerated);
return PwgError.TooFewCharacters;
}
vGenerated[nIndex] = ch;
}
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated);
psOut = new ProtectedString(true, pbUtf8);
MemUtil.ZeroByteArray(pbUtf8);
MemUtil.ZeroArray<char>(vGenerated);
return PwgError.Success;
}
}
}

View File

@@ -1,173 +0,0 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator
{
internal static class PatternBasedGenerator
{
internal static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crsRandomSource)
{
psOut = ProtectedString.Empty;
LinkedList<char> vGenerated = new LinkedList<char>();
PwCharSet pcsCurrent = new PwCharSet();
PwCharSet pcsCustom = new PwCharSet();
PwCharSet pcsUsed = new PwCharSet();
bool bInCharSetDef = false;
string strPattern = ExpandPattern(pwProfile.Pattern);
if(strPattern.Length == 0) return PwgError.Success;
CharStream csStream = new CharStream(strPattern);
char ch = csStream.ReadChar();
while(ch != char.MinValue)
{
pcsCurrent.Clear();
bool bGenerateChar = false;
if(ch == '\\')
{
ch = csStream.ReadChar();
if(ch == char.MinValue) // Backslash at the end
{
vGenerated.AddLast('\\');
break;
}
if(bInCharSetDef) pcsCustom.Add(ch);
else
{
vGenerated.AddLast(ch);
pcsUsed.Add(ch);
}
}
else if(ch == '[')
{
pcsCustom.Clear();
bInCharSetDef = true;
}
else if(ch == ']')
{
pcsCurrent.Add(pcsCustom.ToString());
bInCharSetDef = false;
bGenerateChar = true;
}
else if(bInCharSetDef)
{
if(pcsCustom.AddCharSet(ch) == false)
pcsCustom.Add(ch);
}
else if(pcsCurrent.AddCharSet(ch) == false)
{
vGenerated.AddLast(ch);
pcsUsed.Add(ch);
}
else bGenerateChar = true;
if(bGenerateChar)
{
PwGenerator.PrepareCharSet(pcsCurrent, pwProfile);
if(pwProfile.NoRepeatingCharacters)
pcsCurrent.Remove(pcsUsed.ToString());
char chGen = PwGenerator.GenerateCharacter(pwProfile,
pcsCurrent, crsRandomSource);
if(chGen == char.MinValue) return PwgError.TooFewCharacters;
vGenerated.AddLast(chGen);
pcsUsed.Add(chGen);
}
ch = csStream.ReadChar();
}
if(vGenerated.Count == 0) return PwgError.Success;
char[] vArray = new char[vGenerated.Count];
vGenerated.CopyTo(vArray, 0);
if(pwProfile.PatternPermutePassword)
PwGenerator.ShufflePassword(vArray, crsRandomSource);
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray);
psOut = new ProtectedString(true, pbUtf8);
MemUtil.ZeroByteArray(pbUtf8);
MemUtil.ZeroArray<char>(vArray);
vGenerated.Clear();
return PwgError.Success;
}
private static string ExpandPattern(string strPattern)
{
Debug.Assert(strPattern != null); if(strPattern == null) return string.Empty;
string str = strPattern;
while(true)
{
int nOpen = FindFirstUnescapedChar(str, '{');
int nClose = FindFirstUnescapedChar(str, '}');
if((nOpen >= 0) && (nOpen < nClose))
{
string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1);
str = str.Remove(nOpen, nClose - nOpen + 1);
uint uRepeat;
if(StrUtil.TryParseUInt(strCount, out uRepeat) && (nOpen >= 1))
{
if(uRepeat == 0)
str = str.Remove(nOpen - 1, 1);
else
str = str.Insert(nOpen, new string(str[nOpen - 1], (int)uRepeat - 1));
}
}
else break;
}
return str;
}
private static int FindFirstUnescapedChar(string str, char ch)
{
for(int i = 0; i < str.Length; ++i)
{
char chCur = str[i];
if(chCur == '\\') ++i; // Next is escaped, skip it
else if(chCur == ch) return i;
}
return -1;
}
}
}

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -19,333 +19,311 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator namespace KeePassLib.Cryptography.PasswordGenerator
{ {
public sealed class PwCharSet public sealed class PwCharSet : IEquatable<PwCharSet>
{ {
public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static readonly string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string LowerCase = "abcdefghijklmnopqrstuvwxyz"; public static readonly string LowerCase = "abcdefghijklmnopqrstuvwxyz";
public const string Digits = "0123456789"; public static readonly string Digits = "0123456789";
public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; public static readonly string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ";
public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; public static readonly string LowerConsonants = "bcdfghjklmnpqrstvwxyz";
public const string UpperVowels = "AEIOU"; public static readonly string UpperVowels = "AEIOU";
public const string LowerVowels = "aeiou"; public static readonly string LowerVowels = "aeiou";
public const string Punctuation = @",.;:"; public static readonly string Punctuation = ",.;:";
public const string Brackets = @"[]{}()<>"; public static readonly string Brackets = @"[]{}()<>";
public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; public static readonly string Special = "!\"#$%&'*+,./:;=?@\\^`|~";
public static readonly string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
public const string UpperHex = "0123456789ABCDEF"; public static readonly string UpperHex = "0123456789ABCDEF";
public const string LowerHex = "0123456789abcdef"; public static readonly string LowerHex = "0123456789abcdef";
public const string Invalid = "\t\r\n"; public static readonly string LookAlike = "O0Il1|";
public const string LookAlike = @"O0l1I|";
internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; /// <summary>
/// Latin-1 Supplement except U+00A0 (NBSP) and U+00AD (SHY).
/// </summary>
public static readonly string Latin1S =
"\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7" +
"\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF" +
"\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7" +
"\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF" +
"\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7" +
"\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF" +
"\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7" +
"\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF" +
"\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7" +
"\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF" +
"\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7" +
"\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF";
private const int CharTabSize = (0x10000 / 8); // internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
private List<char> m_vChars = new List<char>(); [Obsolete]
private byte[] m_vTab = new byte[CharTabSize]; public static string SpecialChars { get { return PwCharSet.Special; } }
[Obsolete]
public static string HighAnsiChars { get { return PwCharSet.Latin1S; } }
private static string m_strHighAnsi = null; private readonly List<char> m_lChars = new List<char>();
public static string HighAnsiChars private readonly byte[] m_vTab = new byte[0x10000 / 8];
{
get
{
if(m_strHighAnsi == null) { new PwCharSet(); } // Create string
Debug.Assert(m_strHighAnsi != null);
return m_strHighAnsi;
}
}
private static string m_strSpecial = null; /// <summary>
public static string SpecialChars /// Create a new, empty character set.
{ /// </summary>
get public PwCharSet()
{ {
if(m_strSpecial == null) { new PwCharSet(); } // Create string Debug.Assert(PwCharSet.Latin1S.Length == (16 * 6 - 2));
Debug.Assert(m_strSpecial != null); }
return m_strSpecial;
}
}
/// <summary> public PwCharSet(string strCharSet)
/// Create a new, empty character set collection object. {
/// </summary> Add(strCharSet);
public PwCharSet() }
{
Initialize(true);
}
public PwCharSet(string strCharSet) /// <summary>
{ /// Number of characters in this set.
Initialize(true); /// </summary>
Add(strCharSet); public uint Size
} {
get { return (uint)m_lChars.Count; }
}
private PwCharSet(bool bFullInitialize) /// <summary>
{ /// Get a character of the set using an index.
Initialize(bFullInitialize); /// </summary>
} /// <param name="uPos">Index of the character to get.</param>
/// <returns>Character at the specified position. If the index is invalid,
/// an <c>ArgumentOutOfRangeException</c> is thrown.</returns>
public char this[uint uPos]
{
get
{
if (uPos >= (uint)m_lChars.Count)
throw new ArgumentOutOfRangeException("uPos");
private void Initialize(bool bFullInitialize) return m_lChars[(int)uPos];
{ }
Clear(); }
if(!bFullInitialize) return; public bool Equals(PwCharSet other)
{
if (object.ReferenceEquals(other, this)) return true;
if (object.ReferenceEquals(other, null)) return false;
if(m_strHighAnsi == null) if (m_lChars.Count != other.m_lChars.Count) return false;
{
StringBuilder sbHighAnsi = new StringBuilder();
// [U+0080, U+009F] are C1 control characters,
// U+00A0 is non-breaking space
for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch)
sbHighAnsi.Append(ch);
// U+00AD is soft hyphen (format character)
for(char ch = '\u00AE'; ch < '\u00FF'; ++ch)
sbHighAnsi.Append(ch);
sbHighAnsi.Append('\u00FF');
m_strHighAnsi = sbHighAnsi.ToString(); return MemUtil.ArraysEqual(m_vTab, other.m_vTab);
} }
if(m_strSpecial == null) public override bool Equals(object obj)
{ {
PwCharSet pcs = new PwCharSet(false); return Equals(obj as PwCharSet);
pcs.AddRange('!', '/'); }
pcs.AddRange(':', '@');
pcs.AddRange('[', '`');
pcs.Add(@"|~");
pcs.Remove(@"-_ ");
pcs.Remove(PwCharSet.Brackets);
m_strSpecial = pcs.ToString(); public override int GetHashCode()
} {
} return (int)MemUtil.Hash32(m_vTab, 0, m_vTab.Length);
}
/// <summary> /// <summary>
/// Number of characters in this set. /// Remove all characters from this set.
/// </summary> /// </summary>
public uint Size public void Clear()
{ {
get { return (uint)m_vChars.Count; } m_lChars.Clear();
} Array.Clear(m_vTab, 0, m_vTab.Length);
}
/// <summary> public bool Contains(char ch)
/// Get a character of the set using an index. {
/// </summary> return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue);
/// <param name="uPos">Index of the character to get.</param> }
/// <returns>Character at the specified position. If the index is invalid,
/// an <c>ArgumentOutOfRangeException</c> is thrown.</returns>
public char this[uint uPos]
{
get
{
if(uPos >= (uint)m_vChars.Count)
throw new ArgumentOutOfRangeException("uPos");
return m_vChars[(int)uPos]; public bool Contains(string strCharacters)
} {
} Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
/// <summary> foreach (char ch in strCharacters)
/// Remove all characters from this set. {
/// </summary> if (!Contains(ch)) return false;
public void Clear() }
{
m_vChars.Clear();
Array.Clear(m_vTab, 0, m_vTab.Length);
}
public bool Contains(char ch) return true;
{ }
return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue);
}
public bool Contains(string strCharacters) /// <summary>
{ /// Add characters to the set.
Debug.Assert(strCharacters != null); /// </summary>
if(strCharacters == null) throw new ArgumentNullException("strCharacters"); /// <param name="ch">Character to add.</param>
public void Add(char ch)
{
if (ch == char.MinValue) { Debug.Assert(false); return; }
foreach(char ch in strCharacters) if (!Contains(ch))
{ {
if(!Contains(ch)) return false; m_lChars.Add(ch);
} m_vTab[ch / 8] |= (byte)(1 << (ch % 8));
}
}
return true; /// <summary>
} /// Add characters to the set.
/// </summary>
/// <param name="strCharSet">String containing characters to add.</param>
public void Add(string strCharSet)
{
Debug.Assert(strCharSet != null);
if (strCharSet == null) throw new ArgumentNullException("strCharSet");
/// <summary> foreach (char ch in strCharSet)
/// Add characters to the set. Add(ch);
/// </summary> }
/// <param name="ch">Character to add.</param>
public void Add(char ch)
{
if(ch == char.MinValue) { Debug.Assert(false); return; }
if(!Contains(ch)) public void Add(string strCharSet1, string strCharSet2)
{ {
m_vChars.Add(ch); Add(strCharSet1);
m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); Add(strCharSet2);
} }
}
/// <summary> public void Add(string strCharSet1, string strCharSet2, string strCharSet3)
/// Add characters to the set. {
/// </summary> Add(strCharSet1);
/// <param name="strCharSet">String containing characters to add.</param> Add(strCharSet2);
public void Add(string strCharSet) Add(strCharSet3);
{ }
Debug.Assert(strCharSet != null);
if(strCharSet == null) throw new ArgumentNullException("strCharSet");
m_vChars.Capacity = m_vChars.Count + strCharSet.Length; public void AddRange(char chMin, char chMax)
{
for (char ch = chMin; ch < chMax; ++ch)
Add(ch);
foreach(char ch in strCharSet) Add(chMax);
Add(ch); }
}
public void Add(string strCharSet1, string strCharSet2) public bool AddCharSet(char chCharSetIdentifier)
{ {
Add(strCharSet1); bool bResult = true;
Add(strCharSet2);
}
public void Add(string strCharSet1, string strCharSet2, string strCharSet3) switch (chCharSetIdentifier)
{ {
Add(strCharSet1); case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
Add(strCharSet2); case 'A':
Add(strCharSet3); Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
} PwCharSet.Digits); break;
case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break;
case 'c': Add(PwCharSet.LowerConsonants); break;
case 'C':
Add(PwCharSet.LowerConsonants,
PwCharSet.UpperConsonants); break;
case 'z': Add(PwCharSet.UpperConsonants); break;
case 'd': Add(PwCharSet.Digits); break; // Digit
case 'h': Add(PwCharSet.LowerHex); break;
case 'H': Add(PwCharSet.UpperHex); break;
case 'l': Add(PwCharSet.LowerCase); break;
case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break;
case 'u': Add(PwCharSet.UpperCase); break;
case 'p': Add(PwCharSet.Punctuation); break;
case 'b': Add(PwCharSet.Brackets); break;
case 's': Add(PwCharSet.PrintableAsciiSpecial); break;
case 'S':
Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break;
case 'v': Add(PwCharSet.LowerVowels); break;
case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break;
case 'Z': Add(PwCharSet.UpperVowels); break;
case 'x': Add(PwCharSet.Latin1S); break;
default: bResult = false; break;
}
public void AddRange(char chMin, char chMax) return bResult;
{ }
m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1;
for(char ch = chMin; ch < chMax; ++ch) public bool Remove(char ch)
Add(ch); {
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
return m_lChars.Remove(ch);
}
Add(chMax); public bool Remove(string strCharacters)
} {
Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
public bool AddCharSet(char chCharSetIdentifier) bool bResult = true;
{ foreach (char ch in strCharacters)
bool bResult = true; {
if (!Remove(ch)) bResult = false;
}
switch(chCharSetIdentifier) return bResult;
{ }
case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
PwCharSet.Digits); break;
case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break;
case 'c': Add(PwCharSet.LowerConsonants); break;
case 'C': Add(PwCharSet.LowerConsonants,
PwCharSet.UpperConsonants); break;
case 'z': Add(PwCharSet.UpperConsonants); break;
case 'd': Add(PwCharSet.Digits); break; // Digit
case 'h': Add(PwCharSet.LowerHex); break;
case 'H': Add(PwCharSet.UpperHex); break;
case 'l': Add(PwCharSet.LowerCase); break;
case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break;
case 'u': Add(PwCharSet.UpperCase); break;
case 'p': Add(PwCharSet.Punctuation); break;
case 'b': Add(PwCharSet.Brackets); break;
case 's': Add(PwCharSet.PrintableAsciiSpecial); break;
case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break;
case 'v': Add(PwCharSet.LowerVowels); break;
case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break;
case 'Z': Add(PwCharSet.UpperVowels); break;
case 'x': Add(m_strHighAnsi); break;
default: bResult = false; break;
}
return bResult; public bool RemoveIfAllExist(string strCharacters)
} {
Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
public bool Remove(char ch) if (!Contains(strCharacters))
{ return false;
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
return m_vChars.Remove(ch);
}
public bool Remove(string strCharacters) return Remove(strCharacters);
{ }
Debug.Assert(strCharacters != null);
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
bool bResult = true; /// <summary>
foreach(char ch in strCharacters) /// Convert the character set to a string containing all its characters.
{ /// </summary>
if(!Remove(ch)) bResult = false; /// <returns>String containing all character set characters.</returns>
} public override string ToString()
{
StringBuilder sb = new StringBuilder(m_lChars.Count);
foreach (char ch in m_lChars)
sb.Append(ch);
return bResult; return sb.ToString();
} }
public bool RemoveIfAllExist(string strCharacters) public string PackAndRemoveCharRanges()
{ {
Debug.Assert(strCharacters != null); StringBuilder sb = new StringBuilder();
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
if(!Contains(strCharacters)) sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_');
return false; sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Special) ? 'S' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_');
sb.Append(RemoveIfAllExist("-") ? 'm' : '_');
sb.Append(RemoveIfAllExist("_") ? 'u' : '_');
sb.Append(RemoveIfAllExist(" ") ? 's' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Latin1S) ? 'H' : '_');
return Remove(strCharacters); return sb.ToString();
} }
/// <summary> public void UnpackCharRanges(string strRanges)
/// Convert the character set to a string containing all its characters. {
/// </summary> if (strRanges == null) { Debug.Assert(false); return; }
/// <returns>String containing all character set characters.</returns> if (strRanges.Length < 10) { Debug.Assert(false); return; }
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach(char ch in m_vChars)
sb.Append(ch);
return sb.ToString(); if (strRanges[0] != '_') Add(PwCharSet.UpperCase);
} if (strRanges[1] != '_') Add(PwCharSet.LowerCase);
if (strRanges[2] != '_') Add(PwCharSet.Digits);
public string PackAndRemoveCharRanges() if (strRanges[3] != '_') Add(PwCharSet.Special);
{ if (strRanges[4] != '_') Add(PwCharSet.Punctuation);
StringBuilder sb = new StringBuilder(); if (strRanges[5] != '_') Add('-');
if (strRanges[6] != '_') Add('_');
sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); if (strRanges[7] != '_') Add(' ');
sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); if (strRanges[8] != '_') Add(PwCharSet.Brackets);
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); if (strRanges[9] != '_') Add(PwCharSet.Latin1S);
sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_'); }
sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); }
sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_');
sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_');
sb.Append(RemoveIfAllExist(@" ") ? 's' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_');
return sb.ToString();
}
public void UnpackCharRanges(string strRanges)
{
if(strRanges == null) { Debug.Assert(false); return; }
if(strRanges.Length < 10) { Debug.Assert(false); return; }
if(strRanges[0] != '_') Add(PwCharSet.UpperCase);
if(strRanges[1] != '_') Add(PwCharSet.LowerCase);
if(strRanges[2] != '_') Add(PwCharSet.Digits);
if(strRanges[3] != '_') Add(m_strSpecial);
if(strRanges[4] != '_') Add(PwCharSet.Punctuation);
if(strRanges[5] != '_') Add('-');
if(strRanges[6] != '_') Add('_');
if(strRanges[7] != '_') Add(' ');
if(strRanges[8] != '_') Add(PwCharSet.Brackets);
if(strRanges[9] != '_') Add(m_strHighAnsi);
}
}
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -20,133 +20,172 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Cryptography;
using System.Text; using System.Text;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
using KeePassLib.Resources;
using KeePassLib.Security; using KeePassLib.Security;
using KeePassLib.Utility; using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator namespace KeePassLib.Cryptography.PasswordGenerator
{ {
public enum PwgError public enum PwgError
{ {
Success = 0, Success = 0,
Unknown = 1, Unknown = 1,
TooFewCharacters = 2, TooFewCharacters = 2,
UnknownAlgorithm = 3 UnknownAlgorithm = 3,
} InvalidCharSet = 4,
InvalidPattern = 5
}
/// <summary> /// <summary>
/// Utility functions for generating random passwords. /// Password generator.
/// </summary> /// </summary>
public static class PwGenerator public static class PwGenerator
{ {
public static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, byte[] pbUserEntropy,
CustomPwGeneratorPool pwAlgorithmPool)
{
Debug.Assert(pwProfile != null);
if (pwProfile == null) throw new ArgumentNullException("pwProfile");
CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy); private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy,
PwgError e = PwgError.Unknown; out byte[] pbKey)
{
pbKey = CryptoRandom.Instance.GetRandomBytes(128);
if (pwProfile.GeneratorType == PasswordGeneratorType.CharSet) // Mix in additional entropy
e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs); Debug.Assert(pbKey.Length >= 64);
else if (pwProfile.GeneratorType == PasswordGeneratorType.Pattern) if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length != 0))
e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs); {
else if (pwProfile.GeneratorType == PasswordGeneratorType.Custom) using (SHA512Managed h = new SHA512Managed())
e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool); {
else { Debug.Assert(false); psOut = ProtectedString.Empty; } byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
MemUtil.ZeroByteArray(pbHash);
}
}
return e; return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
} }
private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) internal static char GenerateCharacter(PwCharSet pwCharSet,
{ CryptoRandomStream crsRandomSource)
byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128); {
uint cc = pwCharSet.Size;
if (cc == 0) return char.MinValue;
// Mix in additional entropy uint i = (uint)crsRandomSource.GetRandomUInt64(cc);
Debug.Assert(pbKey.Length >= 64); return pwCharSet[i];
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) }
{
using (SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
}
}
return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
} {
uint cc = pwCharSet.Size;
for (uint i = 0; i < cc; ++i)
{
char ch = pwCharSet[i];
if ((ch == char.MinValue) || (ch == '\t') || (ch == '\r') ||
(ch == '\n') || char.IsSurrogate(ch))
return false;
}
internal static char GenerateCharacter(PwProfile pwProfile, if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
PwCharSet pwCharSet, CryptoRandomStream crsRandomSource)
{
if (pwCharSet.Size == 0) return char.MinValue;
ulong uIndex = crsRandomSource.GetRandomUInt64(); if (!string.IsNullOrEmpty(pwProfile.ExcludeCharacters))
uIndex %= (ulong)pwCharSet.Size; pwCharSet.Remove(pwProfile.ExcludeCharacters);
char ch = pwCharSet[(uint)uIndex]; return true;
}
if (pwProfile.NoRepeatingCharacters) internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource)
pwCharSet.Remove(ch); {
if (v == null) { Debug.Assert(false); return; }
if (crsRandomSource == null) { Debug.Assert(false); return; }
return ch; for (int i = v.Length - 1; i >= 1; --i)
} {
int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1));
internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) char t = v[i];
{ v[i] = v[j];
pwCharSet.Remove(PwCharSet.Invalid); v[j] = t;
}
}
if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); private static PwgError GenerateCustom(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crs,
CustomPwGeneratorPool pwAlgorithmPool)
{
psOut = ProtectedString.Empty;
if (pwProfile.ExcludeCharacters.Length > 0) Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom);
pwCharSet.Remove(pwProfile.ExcludeCharacters); if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm;
}
internal static void ShufflePassword(char[] pPassword, string strID = pwProfile.CustomAlgorithmUuid;
CryptoRandomStream crsRandomSource) if (string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm;
{
Debug.Assert(pPassword != null); if (pPassword == null) return;
Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return;
if (pPassword.Length <= 1) return; // Nothing to shuffle byte[] pbUuid = Convert.FromBase64String(strID);
PwUuid uuid = new PwUuid(pbUuid);
CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid);
if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect) ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs);
{ if (pwd == null) return PwgError.Unknown;
ulong uRandomIndex = crsRandomSource.GetRandomUInt64();
uRandomIndex %= (ulong)(pPassword.Length - nSelect);
char chTemp = pPassword[nSelect]; psOut = pwd;
pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex]; return PwgError.Success;
pPassword[nSelect + (int)uRandomIndex] = chTemp; }
}
}
private static PwgError GenerateCustom(out ProtectedString psOut, internal static string ErrorToString(PwgError e, bool bHeader)
PwProfile pwProfile, CryptoRandomStream crs, {
CustomPwGeneratorPool pwAlgorithmPool) if (e == PwgError.Success) { Debug.Assert(false); return string.Empty; }
{ if ((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed;
psOut = ProtectedString.Empty;
Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); string str = KLRes.UnknownError;
if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; switch (e)
{
// case PwgError.Success:
// break;
string strID = pwProfile.CustomAlgorithmUuid; case PwgError.Unknown:
if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } break;
byte[] pbUuid = Convert.FromBase64String(strID); case PwgError.TooFewCharacters:
PwUuid uuid = new PwUuid(pbUuid); str = KLRes.CharSetTooFewChars;
CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); break;
if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); case PwgError.UnknownAlgorithm:
if (pwd == null) return PwgError.Unknown; str = KLRes.AlgorithmUnknown;
break;
psOut = pwd; case PwgError.InvalidCharSet:
return PwgError.Success; str = KLRes.CharSetInvalid;
} break;
}
case PwgError.InvalidPattern:
str = KLRes.PatternInvalid;
break;
default:
Debug.Assert(false);
break;
}
if (bHeader)
str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
return str;
}
internal static string ErrorToString(Exception ex, bool bHeader)
{
string str = ((ex == null) ? KLRes.UnknownError :
StrUtil.FormatException(ex));
if (bHeader)
str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
return str;
}
}
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -19,114 +19,115 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using KeePassLib.Utility; using KeePassLib.Utility;
namespace KeePassLib.Cryptography namespace KeePassLib.Cryptography
{ {
public static class PopularPasswords public static class PopularPasswords
{ {
private static Dictionary<int, Dictionary<string, bool>> m_dicts = private static readonly Dictionary<int, Dictionary<char[], bool>> g_dicts =
new Dictionary<int, Dictionary<string, bool>>(); new Dictionary<int, Dictionary<char[], bool>>();
internal static int MaxLength internal static int MaxLength
{ {
get get
{ {
int iMaxLen = 0; Debug.Assert(g_dicts.Count > 0); // Should be initialized
foreach(int iLen in m_dicts.Keys)
{
if(iLen > iMaxLen) iMaxLen = iLen;
}
return iMaxLen; int iMaxLen = 0;
} foreach (int iLen in g_dicts.Keys)
} {
if (iLen > iMaxLen) iMaxLen = iLen;
}
internal static bool ContainsLength(int nLength) return iMaxLen;
{ }
Dictionary<string, bool> dDummy; }
return m_dicts.TryGetValue(nLength, out dDummy);
}
public static bool IsPopularPassword(char[] vPassword) internal static bool ContainsLength(int nLength)
{ {
ulong uDummy; Dictionary<char[], bool> dDummy;
return IsPopularPassword(vPassword, out uDummy); return g_dicts.TryGetValue(nLength, out dDummy);
} }
public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) public static bool IsPopularPassword(char[] vPassword)
{ {
if(vPassword == null) throw new ArgumentNullException("vPassword"); ulong uDummy;
if(vPassword.Length == 0) { uDictSize = 0; return false; } return IsPopularPassword(vPassword, out uDummy);
}
string str = new string(vPassword); public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize)
{
if (vPassword == null) throw new ArgumentNullException("vPassword");
if (vPassword.Length == 0) { uDictSize = 0; return false; }
try { return IsPopularPasswordPriv(str, out uDictSize); } #if DEBUG
catch(Exception) { Debug.Assert(false); } Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch)));
#endif
uDictSize = 0; try { return IsPopularPasswordPriv(vPassword, out uDictSize); }
return false; catch (Exception) { Debug.Assert(false); }
}
private static bool IsPopularPasswordPriv(string str, out ulong uDictSize) uDictSize = 0;
{ return false;
Debug.Assert(m_dicts.Count > 0); // Should be initialized with data }
Dictionary<string, bool> d; private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize)
if(!m_dicts.TryGetValue(str.Length, out d)) {
{ Debug.Assert(g_dicts.Count > 0); // Should be initialized with data
uDictSize = 0;
return false;
}
uDictSize = (ulong)d.Count; Dictionary<char[], bool> d;
return d.ContainsKey(str); if (!g_dicts.TryGetValue(vPassword.Length, out d))
} {
uDictSize = 0;
return false;
}
public static void Add(byte[] pbData, bool bGZipped) uDictSize = (ulong)d.Count;
{ return d.ContainsKey(vPassword);
try }
{
if(bGZipped)
pbData = MemUtil.Decompress(pbData);
string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); public static void Add(byte[] pbData, bool bGZipped)
if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } {
try
{
if (bGZipped)
pbData = MemUtil.Decompress(pbData);
if(!char.IsWhiteSpace(strData[strData.Length - 1])) string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length);
strData += "\n"; if (string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for(int i = 0; i < strData.Length; ++i) for (int i = 0; i <= strData.Length; ++i)
{ {
char ch = strData[i]; char ch = ((i == strData.Length) ? ' ' : strData[i]);
if(char.IsWhiteSpace(ch)) if (char.IsWhiteSpace(ch))
{ {
int cc = sb.Length; int cc = sb.Length;
if(cc > 0) if (cc > 0)
{ {
string strWord = sb.ToString(); char[] vWord = new char[cc];
Debug.Assert(strWord.Length == cc); sb.CopyTo(0, vWord, 0, cc);
Dictionary<string, bool> d; Dictionary<char[], bool> d;
if(!m_dicts.TryGetValue(cc, out d)) if (!g_dicts.TryGetValue(cc, out d))
{ {
d = new Dictionary<string, bool>(); d = new Dictionary<char[], bool>(MemUtil.ArrayHelperExOfChar);
m_dicts[cc] = d; g_dicts[cc] = d;
} }
d[strWord] = true; d[vWord] = true;
sb.Remove(0, cc); sb.Remove(0, cc);
} }
} }
else sb.Append(char.ToLower(ch)); else sb.Append(char.ToLower(ch));
} }
} }
catch(Exception) { Debug.Assert(false); } catch (Exception) { Debug.Assert(false); }
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -46,4 +46,12 @@ namespace KeePassLib.Delegates
public delegate void VoidDelegate(); public delegate void VoidDelegate();
public delegate string StrPwEntryDelegate(string str, PwEntry pe); public delegate string StrPwEntryDelegate(string str, PwEntry pe);
public delegate TResult GFunc<TResult>();
public delegate TResult GFunc<T, TResult>(T o);
public delegate TResult GFunc<T1, T2, TResult>(T1 o1, T2 o2);
public delegate TResult GFunc<T1, T2, T3, TResult>(T1 o1, T2 o2, T3 o3);
public delegate TResult GFunc<T1, T2, T3, T4, TResult>(T1 o1, T2 o2, T3 o3, T4 o4);
public delegate TResult GFunc<T1, T2, T3, T4, T5, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5);
public delegate TResult GFunc<T1, T2, T3, T4, T5, T6, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6);
} }

View File

@@ -86,17 +86,12 @@ namespace KeePassLib.Interfaces
/// the current work.</returns> /// the current work.</returns>
bool SetText(string strNewText, LogStatusType lsType); bool SetText(string strNewText, LogStatusType lsType);
void UpdateMessage(String message); /// <summary>
/// Check if the user cancelled the current work.
/// </summary>
void UpdateSubMessage(String submessage); /// <returns>Returns <c>true</c> if the caller should continue
/// the current work.</returns>
/// <summary> bool ContinueWork();
/// Check if the user cancelled the current work.
/// </summary>
/// <returns>Returns <c>true</c> if the caller should continue
/// the current work.</returns>
bool ContinueWork();
} }
public sealed class NullStatusLogger : IStatusLogger public sealed class NullStatusLogger : IStatusLogger
@@ -105,12 +100,6 @@ namespace KeePassLib.Interfaces
public void EndLogging() { } public void EndLogging() { }
public bool SetProgress(uint uPercent) { return true; } public bool SetProgress(uint uPercent) { return true; }
public bool SetText(string strNewText, LogStatusType lsType) { return true; } public bool SetText(string strNewText, LogStatusType lsType) { return true; }
public void UpdateMessage(string message){ public bool ContinueWork() { return true; }
}
public void UpdateSubMessage(string submessage){
}
public bool ContinueWork() { return true; }
} }
} }

View File

@@ -21,7 +21,6 @@ using System.IO;
using Android; using Android;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS;
using Android.Preferences; using Android.Preferences;
using KeePassLib.Serialization; using KeePassLib.Serialization;
@@ -35,17 +34,9 @@ namespace keepass2android
public static void Log(string message) public static void Log(string message)
{ {
if (message != null) if (message != null)
{ Android.Util.Log.Debug("KP2A", message);
message += Thread.CurrentThread.ManagedThreadId != 0 if (LogToFile)
? " (Thread ID: " + Thread.CurrentThread.ManagedThreadId + ")"
: "";
if (Looper.MainLooper == Looper.MyLooper())
message += " (Main Looper)";
Android.Util.Log.Debug("KP2A", message);
}
if (LogToFile)
{ {
lock (_fileLocker) lock (_fileLocker)
{ {
@@ -125,12 +116,23 @@ namespace keepass2android
Intent sendIntent = new Intent(); Intent sendIntent = new Intent();
sendIntent.SetAction(Intent.ActionSend); sendIntent.SetAction(Intent.ActionSend);
sendIntent.PutExtra(Intent.ExtraText, File.ReadAllText(LogFilename)); string logText = File.ReadAllText(LogFilename);
sendIntent.PutExtra(Intent.ExtraText, logText);
sendIntent.PutExtra(Intent.ExtraEmail, "crocoapps@gmail.com"); sendIntent.PutExtra(Intent.ExtraEmail, "crocoapps@gmail.com");
sendIntent.PutExtra(Intent.ExtraSubject, "Keepass2Android log"); sendIntent.PutExtra(Intent.ExtraSubject, "Keepass2Android log");
sendIntent.SetType("text/plain"); sendIntent.SetType("text/plain");
ctx.StartActivity(Intent.CreateChooser(sendIntent, "Send log to...")); try
} {
ctx.StartActivity(Intent.CreateChooser(sendIntent, "Send log to..."));
}
catch (Exception e)
{
Toast.MakeText(ctx, $"Error sending log of length {logText.Length} bytes: " + e.Message, ToastLength.Long)?.Show();
}
}
public static void LogTask(object task, string activityName) public static void LogTask(object task, string activityName)
{ {

View File

@@ -208,7 +208,7 @@ namespace KeePassLib.Serialization
if (!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && if (!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) &&
!m_bRepairMode) !m_bRepairMode)
{ {
// Debug.Assert(m_uFileVersion < FileVersion32_4); Debug.Assert(m_uFileVersion < FileVersion32_4);
byte[] pbHash = Convert.FromBase64String(strHash); byte[] pbHash = Convert.FromBase64String(strHash);
if (!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) if (!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader))

View File

@@ -83,6 +83,7 @@ namespace KeePassLib.Serialization
if (m_bUsedOnce) if (m_bUsedOnce)
throw new InvalidOperationException("Do not reuse KdbxFile objects!"); throw new InvalidOperationException("Do not reuse KdbxFile objects!");
m_bUsedOnce = true; m_bUsedOnce = true;
Kp2aLog.Log("Starting to load KDBX file...");
#if KDBX_BENCHMARK #if KDBX_BENCHMARK
Stopwatch swTime = Stopwatch.StartNew(); Stopwatch swTime = Stopwatch.StartNew();
@@ -257,7 +258,8 @@ namespace KeePassLib.Serialization
MessageService.ShowInfo("Loading KDBX took " + MessageService.ShowInfo("Loading KDBX took " +
swTime.ElapsedMilliseconds.ToString() + " ms."); swTime.ElapsedMilliseconds.ToString() + " ms.");
#endif #endif
} Kp2aLog.Log("Finished loading KDBX file.");
}
private void CommonCleanUpRead(List<Stream> lStreams, HashingStreamEx sHashing) private void CommonCleanUpRead(List<Stream> lStreams, HashingStreamEx sHashing)
{ {
@@ -488,7 +490,7 @@ namespace KeePassLib.Serialization
ProtectedBinary pb = new ProtectedBinary(bProt, pbData, ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
1, pbData.Length - 1); 1, pbData.Length - 1);
//Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication? Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication?
m_pbsBinaries.Add(pb); m_pbsBinaries.Add(pb);
if (bProt) MemUtil.ZeroByteArray(pbData); if (bProt) MemUtil.ZeroByteArray(pbData);

File diff suppressed because it is too large Load Diff

View File

@@ -95,7 +95,7 @@ namespace Kp2aAutofillParserTest
StructureParserBase<TestInputField> parser = StructureParserBase<TestInputField> parser =
new StructureParserBase<TestInputField>(new TestLogger(), new TestDalSourceTrustAll()); new StructureParserBase<TestInputField>(new TestLogger(), new TestDalSourceTrustAll());
var result = parser.ParseForFill(false, autofillView); var result = parser.ParseForFill(autofillView);
if (expectedPackageName != null) if (expectedPackageName != null)
Assert.Equal(expectedPackageName, result.PackageName); Assert.Equal(expectedPackageName, result.PackageName);
if (expectedWebDomain != null) if (expectedWebDomain != null)

View File

@@ -58,7 +58,8 @@
"IsFocused": false, "IsFocused": false,
"InputType": 97, "InputType": 97,
"HtmlInfoTag": null, "HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null "HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "username" ]
}, },
{ {
"IdEntry": "password_text_input_layout", "IdEntry": "password_text_input_layout",
@@ -81,6 +82,7 @@
"InputType": 129, "InputType": 129,
"HtmlInfoTag": null, "HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null, "HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "password" ]
}, },
{ {

View File

@@ -476,8 +476,16 @@ namespace Kp2aAutofillParser
foreach (var field in autofillFields.HintMap.Values.Distinct()) foreach (var field in autofillFields.HintMap.Values.Distinct())
{ {
if (field == null || field.AutofillHints == null)
{
continue;
}
foreach (var hint in field.AutofillHints) foreach (var hint in field.AutofillHints)
{ {
if (hint == null)
{
continue;
}
if (GetPartitionIndex(hint) == partitionIndex) if (GetPartitionIndex(hint) == partitionIndex)
{ {
filteredCollection.Add(field); filteredCollection.Add(field);
@@ -793,14 +801,14 @@ namespace Kp2aAutofillParser
} }
} }
public AutofillTargetId ParseForFill(bool isManual, AutofillView<FieldT> autofillView) public AutofillTargetId ParseForFill(AutofillView<FieldT> autofillView)
{ {
return Parse(true, isManual, autofillView); return Parse(true, autofillView);
} }
public AutofillTargetId ParseForSave(AutofillView<FieldT> autofillView) public AutofillTargetId ParseForSave(AutofillView<FieldT> autofillView)
{ {
return Parse(false, true, autofillView); return Parse(false, autofillView);
} }
/// <summary> /// <summary>
@@ -808,8 +816,7 @@ namespace Kp2aAutofillParser
/// </summary> /// </summary>
/// <returns>The parse.</returns> /// <returns>The parse.</returns>
/// <param name="forFill">If set to <c>true</c> for fill.</param> /// <param name="forFill">If set to <c>true</c> for fill.</param>
/// <param name="isManualRequest"></param> protected virtual AutofillTargetId Parse(bool forFill, AutofillView<FieldT> autofillView)
protected virtual AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<FieldT> autofillView)
{ {
AutofillTargetId result = new AutofillTargetId() AutofillTargetId result = new AutofillTargetId()
{ {
@@ -876,8 +883,9 @@ namespace Kp2aAutofillParser
} }
//for "heuristic determination" we demand that one of the filled fields is focused: //for "heuristic determination" we demand that there is a password field or one of the username fields is focused:
if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused)) //Note that "IsFocused" might be false even when tapping the field. It might require long-press to autofill.
if (passwordFields.Any() || usernameFields.Any(f => f.IsFocused))
{ {
foreach (var uf in usernameFields) foreach (var uf in usernameFields)
AddFieldToHintMap(uf, new string[] { AutofillHintsHelper.AutofillHintUsername }); AddFieldToHintMap(uf, new string[] { AutofillHintsHelper.AutofillHintUsername });

View File

@@ -1,53 +0,0 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using Android.App;
using Android.Content;
using Android.OS;
using Java.Lang;
using Java.Security;
using System.Threading.Tasks;
namespace keepass2android
{
/// <summary>
/// Class to run a task while a progress dialog is shown
/// </summary>
public class BlockingOperationStarter
{
private readonly OperationWithFinishHandler _task;
private readonly IKp2aApp _app;
public BlockingOperationStarter(IKp2aApp app, OperationWithFinishHandler task)
{
_task = task;
_app = app;
}
public void Run()
{
_app.CancelBackgroundOperations();
OperationRunner.Instance.Run(_app, _task, true);
}
}
}

View File

@@ -41,7 +41,7 @@ namespace keepass2android
/// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data /// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data
/// </summary> /// </summary>
/// This also contains methods which are UI specific and should be replacable for testing. /// This also contains methods which are UI specific and should be replacable for testing.
public interface IKp2aApp : ICertificateValidationHandler, IActiveContextProvider public interface IKp2aApp : ICertificateValidationHandler
{ {
/// <summary> /// <summary>
/// Locks all currently open databases, quicklocking if available (unless false is passed for allowQuickUnlock) /// Locks all currently open databases, quicklocking if available (unless false is passed for allowQuickUnlock)
@@ -52,9 +52,7 @@ namespace keepass2android
/// <summary> /// <summary>
/// Loads the specified data as the currently open database, as unlocked. /// Loads the specified data as the currently open database, as unlocked.
/// </summary> /// </summary>
Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent);
IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent,
IDatabaseModificationWatcher modificationWatcher);
HashSet<PwGroup> DirtyGroups { get; } HashSet<PwGroup> DirtyGroups { get; }
@@ -98,6 +96,7 @@ namespace keepass2android
EventHandler<DialogClickEventArgs> yesHandler, EventHandler<DialogClickEventArgs> yesHandler,
EventHandler<DialogClickEventArgs> noHandler, EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler, EventHandler<DialogClickEventArgs> cancelHandler,
Context ctx,
string messageSuffix = ""); string messageSuffix = "");
/// <summary> /// <summary>
@@ -108,6 +107,7 @@ namespace keepass2android
EventHandler<DialogClickEventArgs> yesHandler, EventHandler<DialogClickEventArgs> yesHandler,
EventHandler<DialogClickEventArgs> noHandler, EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler, EventHandler<DialogClickEventArgs> cancelHandler,
Context ctx,
string messageSuffix = ""); string messageSuffix = "");
void ShowMessage(Context ctx, int resourceId, MessageSeverity severity); void ShowMessage(Context ctx, int resourceId, MessageSeverity severity);
@@ -136,17 +136,14 @@ namespace keepass2android
bool CheckForDuplicateUuids { get; } bool CheckForDuplicateUuids { get; }
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE #if !NoNet && !EXCLUDE_JAVAFILESTORAGE
ICertificateErrorHandler CertificateErrorHandler { get; } ICertificateErrorHandler CertificateErrorHandler { get; }
#endif #endif
int WebDavChunkedUploadSize
{
get;
}
bool SyncInBackgroundPreference { get; set; } }
void StartBackgroundSyncService();
ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; }
bool CancelBackgroundOperations();
/// <summary>
/// Registers an action that should be executed when the context instance with the given id has been resumed.
/// </summary>
void RegisterPendingActionForContextInstance(int contextInstanceId, ActionOnOperationFinished actionToPerformWhenContextIsResumed);
}
} }

View File

@@ -186,11 +186,8 @@ namespace keepass2android.Io
Kp2aLog.Log("couldn't open from remote " + ioc.Path); Kp2aLog.Log("couldn't open from remote " + ioc.Path);
#endif #endif
Kp2aLog.Log(ex.ToString()); Kp2aLog.Log(ex.ToString());
if (TriggerWarningWhenFallingBackToCache)
{
_cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
}
_cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
return File.OpenRead(cachedFilePath); return File.OpenRead(cachedFilePath);
} }
} }
@@ -330,10 +327,7 @@ namespace keepass2android.Io
Kp2aLog.Log("couldn't save to remote " + ioc.Path); Kp2aLog.Log("couldn't save to remote " + ioc.Path);
Kp2aLog.Log(e.ToString()); Kp2aLog.Log(e.ToString());
//notify the supervisor so it might display a warning or schedule a retry //notify the supervisor so it might display a warning or schedule a retry
if (TriggerWarningWhenFallingBackToCache) _cacheSupervisor.CouldntSaveToRemote(ioc, e);
{
_cacheSupervisor.CouldntSaveToRemote(ioc, e); }
return false; return false;
} }
} }
@@ -638,9 +632,7 @@ namespace keepass2android.Io
set { _cachedStorage.IsOffline = value; } set { _cachedStorage.IsOffline = value; }
} }
public bool TriggerWarningWhenFallingBackToCache { get; set; } public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
string[] permissions, Permission[] grantResults) string[] permissions, Permission[] grantResults)
{ {
_cachedStorage.OnRequestPermissionsResult(fileStorageSetupActivity, requestCode, permissions, grantResults); _cachedStorage.OnRequestPermissionsResult(fileStorageSetupActivity, requestCode, permissions, grantResults);

View File

@@ -15,7 +15,9 @@ namespace keepass2android.Io
{ {
get { return false; } get { return false; }
} }
}
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
}
public partial class DropboxAppFolderFileStorage: JavaFileStorage public partial class DropboxAppFolderFileStorage: JavaFileStorage
{ {
@@ -29,6 +31,7 @@ namespace keepass2android.Io
get { return false; } get { return false; }
} }
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
} }
} }

View File

@@ -1,13 +0,0 @@
namespace keepass2android.Io
{
public partial class DropboxFileStorage
{
private const string AppKey = "dummy";
private const string AppSecret = "dummy";
}
public partial class DropboxAppFolderFileStorage
{
private const string AppKey = "dummy";
private const string AppSecret = "dummy";
}
}

View File

@@ -0,0 +1,27 @@
<Project>
<Target Name="GenerateDropboxSecrets" BeforeTargets="BeforeCompile"
Inputs="@(DropboxSecretLines)"
Outputs="DropboxFileStorage.g.cs">
<WriteLinesToFile
File="Io/DropboxFileStorage.g.cs"
Lines="@(DropboxSecretLines->'%(Text)')"
Overwrite="true"
/>
</Target>
<ItemGroup>
<DropboxSecretLines Include="GeneratedDropboxSecrets">
<Text>namespace keepass2android.Io {
public partial class DropboxFileStorage {
private const string AppKey = "$(DropboxAppKey)";
private const string AppSecret = "$(DropboxAppSecret)";
}
public partial class DropboxAppFolderFileStorage {
private const string AppKey = "$(DropboxAppFolderAppKey)";
private const string AppSecret = "$(DropboxAppFolderAppSecret)";
}
}</Text>
</DropboxSecretLines>
</ItemGroup>
</Project>

View File

@@ -111,11 +111,6 @@ namespace keepass2android.Io
} }
Java.Lang.Exception exception = e as Java.Lang.Exception; Java.Lang.Exception exception = e as Java.Lang.Exception;
if ((exception is Java.Lang.InterruptedException) || (exception is Java.IO.InterruptedIOException))
{
throw new Java.Lang.InterruptedException(exception.Message);
}
if (exception != null) if (exception != null)
{ {
var ex = new Exception(exception.LocalizedMessage ?? var ex = new Exception(exception.LocalizedMessage ??
@@ -128,7 +123,7 @@ namespace keepass2android.Io
} }
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) public virtual IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{ {
return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this); return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this);
} }

View File

@@ -8,6 +8,7 @@ using Android.Content;
using Android.OS; using Android.OS;
using FluentFTP; using FluentFTP;
using FluentFTP.Exceptions; using FluentFTP.Exceptions;
using FluentFTP.GnuTLS;
using KeePass.Util; using KeePass.Util;
using KeePassLib; using KeePassLib;
using KeePassLib.Serialization; using KeePassLib.Serialization;
@@ -140,6 +141,7 @@ namespace keepass2android.Io
var settings = ConnectionSettings.FromIoc(ioc); var settings = ConnectionSettings.FromIoc(ioc);
FtpClient client = new FtpClient(); FtpClient client = new FtpClient();
client.Config.CustomStream = typeof(GnuTlsStream);
client.Config.RetryAttempts = 3; client.Config.RetryAttempts = 3;
if ((settings.Username.Length > 0) || (settings.Password.Length > 0)) if ((settings.Username.Length > 0) || (settings.Password.Length > 0))
client.Credentials = new NetworkCredential(settings.Username, settings.Password); client.Credentials = new NetworkCredential(settings.Username, settings.Password);

View File

@@ -11,8 +11,7 @@ namespace keepass2android.Io
public interface IOfflineSwitchable public interface IOfflineSwitchable
{ {
bool IsOffline { get; set; } bool IsOffline { get; set; }
bool TriggerWarningWhenFallingBackToCache { get; set; } }
}
/// <summary> /// <summary>
/// Encapsulates another IFileStorage. Allows to switch to offline mode by throwing /// Encapsulates another IFileStorage. Allows to switch to offline mode by throwing
@@ -22,9 +21,8 @@ namespace keepass2android.Io
{ {
private readonly IFileStorage _baseStorage; private readonly IFileStorage _baseStorage;
public bool IsOffline { get; set; } public bool IsOffline { get; set; }
public bool TriggerWarningWhenFallingBackToCache { get; set; }
public OfflineSwitchableFileStorage(IFileStorage baseStorage) public OfflineSwitchableFileStorage(IFileStorage baseStorage)
{ {
_baseStorage = baseStorage; _baseStorage = baseStorage;
} }

View File

@@ -0,0 +1,617 @@
#if !NoNet
using System.Net;
using Android.Content;
using keepass2android;
using keepass2android.Io;
using KeePassLib.Serialization;
using SMBLibrary.Client;
using SMBLibrary;
using FileAttributes = SMBLibrary.FileAttributes;
using KeePassLib.Utility;
using Java.Nio.FileNio;
namespace Kp2aBusinessLogic.Io
{
public class SmbFileStorage : IFileStorage
{
public IEnumerable<string> SupportedProtocols
{
get { yield return "smb"; }
}
public bool UserShouldBackup
{
get { return false; }
}
public void Delete(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
{
return false;
}
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
{
return null;
}
public struct SmbConnectionInfo
{
public string Host;
public string Username;
public string Password;
public string? Domain;
public string? Share;
public string? LocalPath;
public static SmbConnectionInfo FromUrlAndCredentials(string url, string username, string password, string? domain)
{
string userDomain = username;
if (domain != null)
{
userDomain = domain + "\\" + username;
}
if (url.StartsWith("smb://"))
{
url = url.Substring(6);
}
if (url.StartsWith("\\\\"))
{
url = url.Substring(2);
}
url = url.Replace("\\", "/");
string fullPath = "smb://" + WebUtility.UrlEncode(userDomain) + ":" + WebUtility.UrlEncode(password) + "@" + url;
return new SmbConnectionInfo(new IOConnectionInfo() { Path = fullPath} );
}
public SmbConnectionInfo(IOConnectionInfo ioc)
{
string fullpath = ioc.Path;
if (!fullpath.StartsWith("smb://"))
{
throw new Exception("Invalid smb path!");
}
fullpath = fullpath.Substring(6);
string[] authAndPath = fullpath.Split('@');
if (authAndPath.Length != 2)
{
throw new Exception("Invalid smb path!");
}
string[] userAndPwd = authAndPath[0].Split(':');
if (userAndPwd.Length != 2)
{
throw new Exception("Invalid smb path!");
}
string[] pathParts = authAndPath[1].Split('/');
if (pathParts.Length < 1)
{
throw new Exception("Invalid smb path!");
}
Host = pathParts[0];
if (pathParts.Length > 1)
{
Share = pathParts[1];
}
LocalPath = String.Join("/", pathParts.Skip(2));
if (LocalPath.EndsWith("/"))
{
LocalPath = LocalPath.Substring(0, LocalPath.Length - 1);
}
Username = WebUtility.UrlDecode(userAndPwd[0]);
if (Username.Contains("\\"))
{
string[] domainAndUser = Username.Split('\\');
Domain = domainAndUser[0];
Username = domainAndUser[1];
}
else Domain = null;
Password = WebUtility.UrlDecode(userAndPwd[1]);
}
public string ToPath()
{
string domainUser = Username;
if (Domain != null)
{
domainUser = Domain + "\\" + Username;
}
return "smb://" + WebUtility.UrlEncode(domainUser) + ":" + WebUtility.UrlEncode(Password) + "@" + Host +
"/" + Share + "/" + LocalPath;
}
public string GetPathWithoutCredentials()
{
return "smb://" + Host + "/" + Share + "/" + LocalPath;
}
public string GetLocalSmbPath()
{
return LocalPath?.Replace("/", "\\") ?? "";
}
public SmbConnectionInfo GetParent()
{
SmbConnectionInfo parent = new SmbConnectionInfo
{
Host = Host,
Username = Username,
Password = Password,
Domain = Domain,
Share = Share
};
string[] pathParts = LocalPath?.Split('/') ?? [];
if (pathParts.Length > 0)
{
parent.LocalPath = string.Join("/", pathParts.Take(pathParts.Length - 1));
}
else
{
parent.LocalPath = "";
parent.Share = "";
}
return parent;
}
public string Stem()
{
return LocalPath?.Split('/').Last() ?? "";
}
public SmbConnectionInfo GetChild(string childName)
{
SmbConnectionInfo child = new SmbConnectionInfo();
child.Host = Host;
child.Username = Username;
child.Password = Password;
child.Domain = Domain;
if (string.IsNullOrEmpty(Share))
{
child.Share = childName;
}
else
{
child.Share = Share;
var pathPartsList = LocalPath?.Split('/').Where(p => !string.IsNullOrEmpty(p)).ToList() ?? [];
pathPartsList.Add(childName);
child.LocalPath = string.Join("/", pathPartsList);
}
return child;
}
public string ToDisplayString()
{
return "smb://" + Host + "/" + Share + "/" + LocalPath;
}
}
class SmbConnection: IDisposable
{
public SmbConnection(SmbConnectionInfo info)
{
_isLoggedIn = false;
var isConnected = Client.Connect(info.Host, SMBTransportType.DirectTCPTransport);
if (!isConnected)
{
throw new Exception($"Failed to connect to SMB server {info.Host}");
}
var status = Client.Login(info.Domain ?? string.Empty, info.Username, info.Password);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Failed to login to SMB as {info.Username}");
}
_isLoggedIn = true;
if (!string.IsNullOrEmpty(info.Share))
{
FileStore = Client.TreeConnect(info.Share, out status);
}
}
public readonly SMB2Client Client = new SMB2Client();
public readonly ISMBFileStore? FileStore;
private readonly bool _isLoggedIn;
public void Dispose()
{
FileStore?.Disconnect();
if (_isLoggedIn)
Client.Logoff();
if (!Client.IsConnected) return;
Client.Disconnect();
}
}
public Stream OpenFileForRead(IOConnectionInfo ioc)
{
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
using SmbConnection conn = new SmbConnection(info);
if (conn.FileStore == null)
{
throw new Exception($"Failed to read to {info.GetPathWithoutCredentials()}");
}
NTStatus status = conn.FileStore.CreateFile(out var fileHandle, out _, info.GetLocalSmbPath(),
AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.Read,
CreateDisposition.FILE_OPEN,
CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Failed to open file {info.LocalPath}");
}
var stream = new MemoryStream();
long bytesRead = 0;
while (true)
{
status = conn.FileStore.ReadFile(out var data, fileHandle, bytesRead, (int)conn.Client.MaxReadSize);
if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE)
{
throw new Exception("Failed to read from file");
}
if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0)
{
break;
}
bytesRead += data.Length;
stream.Write(data, 0, data.Length);
}
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
class SmbFileStorageWriteTransaction : IWriteTransaction
{
private bool UseFileTransaction { get; }
private readonly string _path;
private readonly string _uploadPath;
private readonly SmbFileStorage _fileStorage;
private MemoryStream? _memoryStream;
public SmbFileStorageWriteTransaction(string path, SmbFileStorage fileStorage, bool useFileTransaction)
{
UseFileTransaction = useFileTransaction;
_path = path;
if (useFileTransaction)
{
_uploadPath = _path + Guid.NewGuid().ToString().Substring(0, 8) + ".tmp";
}
else
{
_uploadPath = _path;
}
_fileStorage = fileStorage;
_memoryStream = null;
}
public void Dispose()
{
_memoryStream?.Dispose();
}
public Stream OpenFile()
{
_memoryStream = new MemoryStream();
return _memoryStream;
}
public void CommitWrite()
{
_fileStorage.UploadData(new MemoryStream(_memoryStream!.ToArray()), new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath}));
if (UseFileTransaction)
{
SmbConnectionInfo uploadPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath });
SmbConnectionInfo finalPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _path });
_fileStorage.RenameFile(uploadPath, finalPath);
}
}
}
private void RenameFile(SmbConnectionInfo fromPath, SmbConnectionInfo toPath)
{
using var connection = new SmbConnection(fromPath);
// Open existing file
var status = connection.FileStore!.CreateFile(out var handle, out _, fromPath.GetLocalSmbPath(), AccessMask.MAXIMUM_ALLOWED, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE, null);
if (status != NTStatus.STATUS_SUCCESS)
throw new Exception($"Failed to open {fromPath.LocalPath} for renaming!");
FileRenameInformationType2 renameInfo = new FileRenameInformationType2
{
FileName = toPath.GetLocalSmbPath(),
ReplaceIfExists = true
};
connection.FileStore.SetFileInformation(handle, renameInfo);
connection.FileStore.CloseFile(handle);
}
private void UploadData(Stream data, SmbConnectionInfo uploadPath)
{
using var connection = new SmbConnection(uploadPath);
var status = connection.FileStore!.CreateFile(out var fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_CREATE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
if (status == NTStatus.STATUS_OBJECT_NAME_COLLISION)
status = connection.FileStore!.CreateFile(out fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_OVERWRITE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Failed to open {uploadPath.LocalPath} for writing!");
}
long writeOffset = 0;
while (data.Position < data.Length)
{
byte[] buffer = new byte[(int)connection.Client.MaxWriteSize];
int bytesRead = data.Read(buffer, 0, buffer.Length);
if (bytesRead < (int)connection.Client.MaxWriteSize)
{
Array.Resize(ref buffer, bytesRead);
}
status = connection.FileStore.WriteFile(out _, fileHandle, writeOffset, buffer);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception("Failed to write to file");
}
writeOffset += bytesRead;
}
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
return new SmbFileStorageWriteTransaction(ioc.Path, this, useFileTransaction);
}
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
return UrlUtil.StripExtension(
UrlUtil.GetFileName(ioc.Path));
}
public string GetFileExtension(IOConnectionInfo ioc)
{
return UrlUtil.GetExtension(ioc.Path);
}
public bool RequiresCredentials(IOConnectionInfo ioc)
{
return false;
}
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
{
throw new NotImplementedException();
}
private static IEnumerable<FileDescription> ListShares(SmbConnection conn, SmbConnectionInfo parent)
{
foreach (string share in conn.Client.ListShares(out _))
{
yield return new FileDescription()
{
CanRead = true,
CanWrite = true,
DisplayName = share,
IsDirectory = true,
Path = parent.GetChild(share).ToPath()
};
}
}
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
{
List<FileDescription> result = [];
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
using SmbConnection conn = new SmbConnection(info);
if (string.IsNullOrEmpty(info.Share))
{
var shares = ListShares(conn, info).ToList();
return shares;
}
NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
if (status == NTStatus.STATUS_SUCCESS)
{
conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, "*", FileInformationClass.FileDirectoryInformation);
foreach (var fi in fileList)
{
var fileDirectoryInformation = fi as FileDirectoryInformation;
if (fileDirectoryInformation == null)
continue;
if (fileDirectoryInformation.FileName is "." or "..")
continue;
var fileDescription = FileDescriptionConvert(ioc, fileDirectoryInformation);
result.Add(fileDescription);
}
conn.FileStore.CloseFile(directoryHandle);
}
return result;
}
private FileDescription FileDescriptionConvert(IOConnectionInfo parentIoc,
FileDirectoryInformation fileDirectoryInformation)
{
FileDescription fileDescription = new FileDescription
{
CanRead = true,
CanWrite = true,
IsDirectory = (fileDirectoryInformation.FileAttributes & FileAttributes.Directory) != 0,
DisplayName = fileDirectoryInformation.FileName
};
fileDescription.Path = CreateFilePath(parentIoc.Path, fileDescription.DisplayName);
fileDescription.LastModified = fileDirectoryInformation.LastWriteTime;
fileDescription.SizeInBytes = fileDirectoryInformation.EndOfFile;
return fileDescription;
}
public FileDescription GetFileDescription(IOConnectionInfo ioc)
{
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
if (string.IsNullOrEmpty(info.Share))
{
return new FileDescription
{
CanRead = true, CanWrite = true,
DisplayName = info.Host,
IsDirectory = true,
Path = info.ToPath()
};
}
using SmbConnection conn = new SmbConnection(info);
NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetParent().GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
if (status != NTStatus.STATUS_SUCCESS) throw new Exception($"Failed to query details for {info.LocalPath}");
conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, info.Stem(), FileInformationClass.FileDirectoryInformation);
foreach (var fi in fileList)
{
var fileDirectoryInformation = fi as FileDirectoryInformation;
if (fileDirectoryInformation == null)
continue;
if (fileDirectoryInformation.FileName is "." or "..")
continue;
return FileDescriptionConvert(ioc, fileDirectoryInformation);
}
conn.FileStore.CloseFile(directoryHandle);
throw new Exception($"Failed to query details for {info.LocalPath}");
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
return false;
}
public string IocToPath(IOConnectionInfo ioc)
{
return ioc.Path;
}
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
{
activity.PerformManualFileSelect(isForSave, requestCode, protocolId);
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
Intent intent = new Intent();
activity.IocToIntent(intent, ioc);
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent);
}
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
{
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{
}
public void OnResume(IFileStorageSetupActivity activity)
{
}
public void OnStart(IFileStorageSetupActivity activity)
{
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
{
}
public string GetDisplayName(IOConnectionInfo ioc)
{
return new SmbConnectionInfo(ioc).ToDisplayString();
}
public string CreateFilePath(string parent, string newFilename)
{
return new SmbConnectionInfo(new IOConnectionInfo() { Path = parent}).GetChild(newFilename).ToPath();
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
SmbConnectionInfo connectionInfo = new SmbConnectionInfo(ioc);
return new IOConnectionInfo() { Path = connectionInfo.GetParent().ToPath() };
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
{
return new IOConnectionInfo() { Path = CreateFilePath(folderPath.Path, filename)};
}
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
return true;
}
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
{
return false;
}
}
}
#endif

View File

@@ -6,10 +6,12 @@ using System.Text;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Android.Preferences;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE #if !NoNet && !EXCLUDE_JAVAFILESTORAGE
using Keepass2android.Javafilestorage; using Keepass2android.Javafilestorage;
#endif #endif
using KeePassLib.Serialization; using KeePassLib.Serialization;
@@ -19,9 +21,15 @@ namespace keepass2android.Io
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE #if !NoNet && !EXCLUDE_JAVAFILESTORAGE
public class WebDavFileStorage: JavaFileStorage public class WebDavFileStorage: JavaFileStorage
{ {
public WebDavFileStorage(IKp2aApp app) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler), app) private readonly IKp2aApp _app;
{ private readonly WebDavStorage baseWebdavStorage;
}
public WebDavFileStorage(IKp2aApp app, int chunkSize) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler, chunkSize), app)
{
_app = app;
baseWebdavStorage = (WebDavStorage)Jfs;
}
public override IEnumerable<string> SupportedProtocols public override IEnumerable<string> SupportedProtocols
{ {
@@ -75,6 +83,15 @@ namespace keepass2android.Io
} }
return base.IocToPath(ioc); return base.IocToPath(ioc);
} }
}
public override IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
baseWebdavStorage.SetUploadChunkSize(_app.WebDavChunkedUploadSize);
return base.OpenWriteTransaction(ioc, useFileTransaction);
}
}
#endif #endif
} }

View File

@@ -4,30 +4,45 @@
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<DefineConstants Condition="'$(Flavor)'=='NoNet'">NO_QR_SCANNER;EXCLUDE_JAVAFILESTORAGE;NoNet</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources\" /> <Folder Include="Resources\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentFTP" Version="51.1.0" /> <PackageReference Include="FluentFTP" Version="52.1.0" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="MegaApiClient" Version="1.10.4" /> <PackageReference Include="FluentFTP.GnuTLS" Version="1.0.37" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Microsoft.Graph" Version="5.68.0" /> <PackageReference Include="MegaApiClient" Version="1.10.4" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" /> <PackageReference Include="Microsoft.Graph" Version="5.68.0" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="SMBLibrary" Version="1.5.4" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Xamarin.AndroidX.Browser" Version="1.8.0" /> <PackageReference Include="Xamarin.AndroidX.Browser" Version="1.8.0" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.13.1.5" /> <PackageReference Include="Xamarin.AndroidX.Core" Version="1.13.1.5" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.11.0.3" /> <PackageReference Include="Xamarin.Google.Android.Material" Version="1.11.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AndroidFileChooserBinding\AndroidFileChooserBinding.csproj" /> <ProjectReference Include="..\AndroidFileChooserBinding\AndroidFileChooserBinding.csproj" />
<ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj" /> <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj" Condition="'$(Flavor)'!='NoNet'" />
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj" /> <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj" />
<ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj" /> <ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj" />
<ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj" /> <ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Io/DropboxFileStorageKeysDummy.cs" />
<Compile Remove="Io/DropboxFileStorageKeysDummy.cs" /> </ItemGroup>
<Content Remove="Io/DropboxFileStorageKeysDummy.cs" /> <ItemGroup Condition="'$(Flavor)'=='NoNet'">
<None Remove="Io/OneDrive2FileStorage.cs" />
<Compile Remove="Io/OneDrive2FileStorage.cs" />
<Content Remove="Io/OneDrive2FileStorage.cs" />
<None Remove="Io/MegaFileStorage.cs" />
<Compile Remove="Io/MegaFileStorage.cs" />
<Content Remove="Io/MegaFileStorage.cs" />
</ItemGroup> </ItemGroup>
<Import Project="Io/GenerateSecrets.targets" />
<ItemGroup>
<Compile Include="Io/DropboxFileStorage.g.cs" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,239 +0,0 @@
using Android.App;
using Android.Content;
using Android.OS;
using System.Threading.Tasks;
using Thread = Java.Lang.Thread;
namespace keepass2android;
/// <summary>
/// Allows to run tasks in the background. The UI is not blocked by the task. Tasks continue to run in the BackgroundSyncService if the app goes to background while tasks are active.
/// </summary>
public class OperationRunner
{
//singleton instance
private static OperationRunner _instance = null;
public static OperationRunner Instance
{
get
{
if (_instance == null)
{
_instance = new OperationRunner();
}
return _instance;
}
}
void Initialize(IKp2aApp app)
{
}
public struct OperationWithMetadata
{
public OperationWithMetadata()
{
Operation = null;
}
public OperationWithFinishHandler Operation { get; set; }
public bool RunBlocking { get; set; } = false;
}
public ProgressUiAsStatusLoggerAdapter StatusLogger => _statusLogger;
private OperationRunner()
{
//private constructor
}
private readonly Queue<OperationWithMetadata> _taskQueue = new Queue<OperationWithMetadata>();
private readonly object _taskQueueLock = new object();
private Java.Lang.Thread? _thread = null;
private OperationWithMetadata? _currentlyRunningTask = null;
private ProgressUiAsStatusLoggerAdapter _statusLogger = null;
private IProgressDialog _progressDialog;
private IKp2aApp _app;
public void Run(IKp2aApp app, OperationWithFinishHandler operation, bool runBlocking = false)
{
lock (Instance._taskQueueLock)
{
_taskQueue.Enqueue(new OperationWithMetadata(){ Operation = operation, RunBlocking = runBlocking});
operation.SetStatusLogger(_statusLogger);
// Start thread to run the task (unless it's already running)
if (_thread == null)
{
_statusLogger.StartLogging("", false);
_thread = new Java.Lang.Thread(() =>
{
while (true)
{
lock (_taskQueueLock)
{
if (!_taskQueue.Any())
{
_thread = null;
_statusLogger.EndLogging();
break;
}
else
{
_currentlyRunningTask = _taskQueue.Dequeue();
}
}
if (_currentlyRunningTask.Value.RunBlocking)
{
app.UiThreadHandler.Post(
() =>
{
TrySetupProgressDialog();
});
}
var originalFinishedHandler = _currentlyRunningTask.Value.Operation.operationFinishedHandler;
_currentlyRunningTask.Value.Operation.operationFinishedHandler = new ActionOnOperationFinished(app, (
(success, message, context) =>
{
if (_currentlyRunningTask?.RunBlocking == true)
{
_app.UiThreadHandler.Post(() =>
{
_progressDialog?.Dismiss();
}
);
}
_currentlyRunningTask = null;
}), originalFinishedHandler);
_currentlyRunningTask.Value.Operation.Run();
while (_currentlyRunningTask != null)
{
try
{
Thread.Sleep(100);
}
catch (Exception e)
{
Kp2aLog.Log("Thread interrupted.");
}
}
}
});
_thread.Start();
}
}
}
private bool TrySetupProgressDialog()
{
string currentMessage = "Initializing...";
string currentSubmessage = "";
if (_statusLogger != null)
{
currentMessage = _statusLogger.LastMessage;
currentSubmessage = _statusLogger.LastSubMessage;
}
if (_progressDialog != null)
{
var pd = _progressDialog;
_app.UiThreadHandler.Post(() =>
{
pd.Dismiss();
});
}
// Show process dialog
_progressDialog = _app.CreateProgressDialog(_app.ActiveContext);
if (_progressDialog == null)
{
return false;
}
var progressUi = new ProgressDialogUi(_app, _app.UiThreadHandler, _progressDialog);
_statusLogger.SetNewProgressUi(progressUi);
_statusLogger.StartLogging("", false);
_statusLogger.UpdateMessage(currentMessage);
_statusLogger.UpdateSubMessage(currentSubmessage);
return true;
}
public void SetNewActiveContext(IKp2aApp app)
{
_app = app;
Context? context = app.ActiveContext;
bool isAppContext = context == null || (context.ApplicationContext == context);
lock (_taskQueueLock)
{
if (isAppContext && _thread != null)
{
//this will register the service as new active context (see BackgroundSyncService.OnStartCommand())
app.StartBackgroundSyncService();
return;
}
if (_currentlyRunningTask?.RunBlocking == true && (context is Activity { IsFinishing: false, IsDestroyed:false}))
{
app.UiThreadHandler.Post(() =>
{
TrySetupProgressDialog();
});
}
else
{
var progressUi = (context as IProgressUiProvider)?.ProgressUi;
if (_statusLogger == null)
{
_statusLogger = new ProgressUiAsStatusLoggerAdapter(progressUi, app);
}
else
{
_statusLogger.SetNewProgressUi(progressUi);
}
}
foreach (var task in _taskQueue.Concat(_currentlyRunningTask == null ?
new List<OperationWithMetadata>() : [_currentlyRunningTask.Value])
)
{
task.Operation.SetStatusLogger(_statusLogger);
}
}
}
public void CancelAll()
{
lock (_taskQueueLock)
{
if (_thread != null)
{
_thread.Interrupt();
_thread = null;
_statusLogger?.EndLogging();
}
_taskQueue.Clear();
_currentlyRunningTask = null;
}
}
}

View File

@@ -22,152 +22,116 @@ using KeePassLib.Interfaces;
namespace keepass2android namespace keepass2android
{ {
public interface IKp2aStatusLogger : IStatusLogger /// <summary>
{ /// StatusLogger implementation which shows the progress in a progress dialog
void UpdateMessage(UiStringKey stringKey); /// </summary>
string LastMessage { get; } public class ProgressDialogStatusLogger: IStatusLogger {
string LastSubMessage { get; }
}
public interface IProgressUi
{
void Show();
void Hide();
void UpdateMessage(String message);
void UpdateSubMessage(String submessage);
}
public interface IProgressUiProvider
{
IProgressUi? ProgressUi { get; }
}
public class Kp2aNullStatusLogger : IKp2aStatusLogger
{
public void StartLogging(string strOperation, bool bWriteOperationToLog)
{
}
public void EndLogging()
{
}
public bool SetProgress(uint uPercent)
{
return true;
}
public bool SetText(string strNewText, LogStatusType lsType)
{
return true;
}
private string _lastMessage;
private string _lastSubMessage;
public void UpdateMessage(string message)
{
_lastMessage = message;
}
public void UpdateSubMessage(string submessage)
{
_lastSubMessage = submessage;
}
public bool ContinueWork()
{
return true;
}
public void UpdateMessage(UiStringKey stringKey)
{
}
public string LastMessage { get { return _lastMessage; } }
public string LastSubMessage { get { return _lastSubMessage; } }
}
/// <summary>
/// StatusLogger implementation which shows the progress in a progress dialog
/// </summary>
public class ProgressDialogUi: IProgressUi
{
private readonly IProgressDialog _progressDialog; private readonly IProgressDialog _progressDialog;
readonly IKp2aApp _app;
private readonly Handler _handler; private readonly Handler _handler;
private string _message = ""; private string _message = "";
private string _submessage; private string _submessage;
private readonly IKp2aApp _app;
public String LastSubMessage => _submessage; public String SubMessage => _submessage;
public String LastMessage => _message; public String Message => _message;
public ProgressDialogStatusLogger() {
public ProgressDialogUi(IKp2aApp app, Handler handler, IProgressDialog pd) }
{
_app = app; public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) {
_app = app;
_progressDialog = pd; _progressDialog = pd;
_handler = handler; _handler = handler;
} }
public void UpdateSubMessage(String submessage) public void UpdateMessage(UiStringKey stringKey) {
{ if (_app != null)
Kp2aLog.Log("status submessage: " + submessage); UpdateMessage(_app.GetResourceString(stringKey));
_submessage = submessage; }
if (_app != null && _progressDialog != null && _handler != null)
{
_handler.Post(() =>
{
if (!String.IsNullOrEmpty(submessage))
{
_progressDialog.SetMessage(_message + " (" + submessage + ")");
}
else
{
_progressDialog.SetMessage(_message);
}
}
);
}
}
public void Show() public void UpdateMessage (String message)
{ {
_handler.Post(() => Kp2aLog.Log("status message: " + message);
{
_progressDialog?.Show();
});
}
public void Hide()
{
_handler.Post(() =>
{
_progressDialog?.Dismiss();
});
}
public void UpdateMessage(string message)
{
Kp2aLog.Log("status message: " + message);
_message = message; _message = message;
if (_app != null && _progressDialog != null && _handler != null) if ( _app!= null && _progressDialog != null && _handler != null ) {
{ _handler.Post(() => {_progressDialog.SetMessage(message); } );
_handler.Post(() => }
{ }
_progressDialog.SetMessage(message);
});
}
}
} public void UpdateSubMessage(String submessage)
{
Kp2aLog.Log("status submessage: " + submessage);
_submessage = submessage;
if (_app != null && _progressDialog != null && _handler != null)
{
_handler.Post(() =>
{
if (!String.IsNullOrEmpty(submessage))
{
_progressDialog.SetMessage(_message + " (" + submessage + ")");
}
else
{
_progressDialog.SetMessage(_message);
}
}
);
}
}
#region IStatusLogger implementation
public void StartLogging (string strOperation, bool bWriteOperationToLog)
{
}
public void EndLogging ()
{
}
public bool SetProgress (uint uPercent)
{
return true;
}
public bool SetText (string strNewText, LogStatusType lsType)
{
if (strNewText.StartsWith("KP2AKEY_"))
{
UiStringKey key;
if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key))
{
UpdateMessage(_app.GetResourceString(key), lsType);
return true;
}
}
UpdateMessage(strNewText, lsType);
return true;
}
private void UpdateMessage(string message, LogStatusType lsType)
{
if (lsType == LogStatusType.AdditionalInfo)
{
UpdateSubMessage(message);
}
else
{
UpdateMessage(message);
}
}
public bool ContinueWork ()
{
return true;
}
#endregion
}
} }

View File

@@ -0,0 +1,179 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using Android.App;
using Android.Content;
using Android.OS;
using Java.Lang;
namespace keepass2android
{
/// <summary>
/// Class to run a task while a progress dialog is shown
/// </summary>
public class ProgressTask
{
//for handling Activity recreation situations, we need access to the currently active task. It must hold that there is no more than one active task.
private static ProgressTask _currentTask = null;
public static void SetNewActiveActivity(Activity activeActivity)
{
if (_currentTask != null)
{
_currentTask.ActiveActivity = activeActivity;
}
}
public static void RemoveActiveActivity(Activity activity)
{
if ((_currentTask != null) && (_currentTask._activeActivity == activity))
_currentTask.ActiveActivity = null;
}
public Activity ActiveActivity
{
get { return _activeActivity; }
private set
{
if (_activeActivity != null && _activeActivity != _previouslyActiveActivity)
{
_previouslyActiveActivity = _activeActivity;
}
_activeActivity = value;
if (_task != null)
_task.ActiveActivity = _activeActivity;
if (_activeActivity != null)
{
SetupProgressDialog(_app);
_progressDialog.Show();
}
}
}
public Activity PreviouslyActiveActivity
{
get { return _previouslyActiveActivity; }
}
private readonly Handler _handler;
private readonly RunnableOnFinish _task;
private IProgressDialog _progressDialog;
private readonly IKp2aApp _app;
private Java.Lang.Thread _thread;
private Activity _activeActivity, _previouslyActiveActivity;
private ProgressDialogStatusLogger _progressDialogStatusLogger;
public ProgressTask(IKp2aApp app, Activity activity, RunnableOnFinish task)
{
_activeActivity = activity;
_task = task;
_handler = app.UiThreadHandler;
_app = app;
SetupProgressDialog(app);
// Set code to run when this is finished
_task.OnFinishToRun = new AfterTask(activity, task.OnFinishToRun, _handler, this);
_task.SetStatusLogger(_progressDialogStatusLogger);
}
private void SetupProgressDialog(IKp2aApp app)
{
string currentMessage = "Initializing...";
string currentSubmessage = "";
if (_progressDialogStatusLogger != null)
{
currentMessage = _progressDialogStatusLogger.Message;
currentSubmessage = _progressDialogStatusLogger.SubMessage;
}
if (_progressDialog != null)
{
var pd = _progressDialog;
app.UiThreadHandler.Post(() =>
{
pd.Dismiss();
});
}
// Show process dialog
_progressDialog = app.CreateProgressDialog(_activeActivity);
_progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title));
_progressDialogStatusLogger = new ProgressDialogStatusLogger(_app, _handler, _progressDialog);
_progressDialogStatusLogger.UpdateMessage(currentMessage);
_progressDialogStatusLogger.UpdateSubMessage(currentSubmessage);
}
public void Run(bool allowOverwriteCurrentTask = false)
{
if ((!allowOverwriteCurrentTask) && (_currentTask != null))
throw new System.Exception("Cannot start another ProgressTask while ProgressTask is already running! " + _task.GetType().Name + "/" + _currentTask._task.GetType().Name);
_currentTask = this;
// Show process dialog
_progressDialog.Show();
// Start Thread to Run task
_thread = new Java.Lang.Thread(_task.Run);
_thread.Start();
}
public void JoinWorkerThread()
{
_thread.Join();
}
private class AfterTask : OnFinish {
readonly ProgressTask _progressTask;
public AfterTask (Activity activity, OnFinish finish, Handler handler, ProgressTask pt): base(activity, finish, handler)
{
_progressTask = pt;
}
public override void Run() {
base.Run();
if (Handler != null) //can be null in tests
{
// Remove the progress dialog
Handler.Post(delegate
{
_progressTask._progressDialog.Dismiss();
});
}
else
{
_progressTask._progressDialog.Dismiss();
}
_currentTask = null;
}
}
}
}

View File

@@ -1,94 +0,0 @@
using KeePassLib.Interfaces;
namespace keepass2android;
public class ProgressUiAsStatusLoggerAdapter : IKp2aStatusLogger
{
private IProgressUi? _progressUi;
private readonly IKp2aApp _app;
private string _lastMessage = "";
private string _lastSubMessage = "";
private bool _isVisible = false;
public ProgressUiAsStatusLoggerAdapter(IProgressUi progressUi, IKp2aApp app)
{
_progressUi = progressUi;
_app = app;
}
public void SetNewProgressUi(IProgressUi progressUi)
{
_progressUi?.Hide();
_progressUi = progressUi;
if (_isVisible)
{
progressUi?.Show();
progressUi?.UpdateMessage(_lastMessage);
progressUi?.UpdateSubMessage(_lastSubMessage);
}
else
{
progressUi?.Hide();
}
}
public void StartLogging(string strOperation, bool bWriteOperationToLog)
{
_progressUi?.Show();
_isVisible = true;
}
public void EndLogging()
{
_progressUi?.Hide();
_isVisible = false;
}
public bool SetProgress(uint uPercent)
{
return true;
}
public bool SetText(string strNewText, LogStatusType lsType)
{
if (strNewText.StartsWith("KP2AKEY_"))
{
UiStringKey key;
if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key))
{
UpdateMessage(_app.GetResourceString(key));
return true;
}
}
UpdateMessage(strNewText);
return true;
}
public void UpdateMessage(string message)
{
_progressUi?.UpdateMessage(message);
_lastMessage = message;
}
public void UpdateSubMessage(string submessage)
{
_progressUi?.UpdateSubMessage(submessage);
_lastSubMessage = submessage;
}
public bool ContinueWork()
{
return !Java.Lang.Thread.Interrupted();
}
public void UpdateMessage(UiStringKey stringKey)
{
if (_app != null)
UpdateMessage(_app.GetResourceString(stringKey));
}
public string LastMessage { get { return _lastMessage; } }
public string LastSubMessage { get { return _lastSubMessage; } }
}

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -16,7 +15,14 @@ namespace KeePass.Util
string errorMessage = e.Message; string errorMessage = e.Message;
if (e is Java.Lang.Exception javaException) if (e is Java.Lang.Exception javaException)
{ {
errorMessage = javaException.Message ?? errorMessage; try
{
errorMessage = javaException.LocalizedMessage ?? javaException.Message ?? errorMessage;
}
finally
{
}
} }
return errorMessage; return errorMessage;

View File

@@ -13,15 +13,16 @@ using keepass2android.Io;
namespace keepass2android namespace keepass2android
{ {
public class CheckDatabaseForChanges: OperationWithFinishHandler public class CheckDatabaseForChanges: RunnableOnFinish
{ {
private readonly Context _context; private readonly Context _context;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
public CheckDatabaseForChanges(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler) public CheckDatabaseForChanges(Activity context, IKp2aApp app, OnFinish finish)
: base(app, operationFinishedHandler) : base(context, finish)
{ {
_context = context;
_app = app; _app = app;
} }

View File

@@ -85,14 +85,17 @@ namespace keepass2android
/// <summary> /// <summary>
/// Do not call this method directly. Call App.Kp2a.LoadDatabase instead. /// Do not call this method directly. Call App.Kp2a.LoadDatabase instead.
/// </summary> /// </summary>
public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, IKp2aStatusLogger status, IDatabaseFormat databaseFormat) public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
{ {
PwDatabase pwDatabase = new PwDatabase(); PwDatabase pwDatabase = new PwDatabase();
IFileStorage fileStorage = _app.GetFileStorage(iocInfo); IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo); Kp2aLog.Log("LoadData: Retrieving stream");
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo);
PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat); Kp2aLog.Log("LoadData: GetCurrentFileVersion");
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
Kp2aLog.Log("LoadData: PopulateDatabaseFromStream");
PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat);
LastFileVersion = fileVersion; LastFileVersion = fileVersion;
status.UpdateSubMessage(""); status.UpdateSubMessage("");
@@ -149,7 +152,7 @@ namespace keepass2android
get { return GetFingerprintModePrefKey(Ioc); } get { return GetFingerprintModePrefKey(Ioc); }
} }
protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, IKp2aStatusLogger status, IDatabaseFormat databaseFormat) protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
{ {
IFileStorage fileStorage = _app.GetFileStorage(iocInfo); IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo); var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo);
@@ -194,9 +197,9 @@ namespace keepass2android
} }
public void SaveData(IFileStorage fileStorage) { public void SaveData() {
using (IWriteTransaction trans = fileStorage.OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions))) using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
{ {
DatabaseFormat.Save(KpDatabase, trans.OpenFile()); DatabaseFormat.Save(KpDatabase, trans.OpenFile());

View File

@@ -396,6 +396,8 @@ namespace keepass2android
{ {
PwGroupV3 toGroup = new PwGroupV3(); PwGroupV3 toGroup = new PwGroupV3();
toGroup.Name = fromGroup.Name; toGroup.Name = fromGroup.Name;
//todo remove
Android.Util.Log.Debug("KP2A", "save kdb: group " + fromGroup.Name);
toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime)); toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime));
toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime)); toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime));

View File

@@ -4,152 +4,109 @@ using System.IO;
using System.Text; using System.Text;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using keepass2android.Io; using keepass2android.Io;
using KeePass.Util; using KeePass.Util;
using Group.Pals.Android.Lib.UI.Filechooser.Utils;
using KeePassLib;
namespace keepass2android namespace keepass2android
{ {
public class SynchronizeCachedDatabase: OperationWithFinishHandler public class SynchronizeCachedDatabase: RunnableOnFinish
{ {
private readonly Activity _context;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private IDatabaseModificationWatcher _modificationWatcher; private SaveDb _saveDb;
private readonly Database _database;
public SynchronizeCachedDatabase(Activity context, IKp2aApp app, OnFinish finish)
public SynchronizeCachedDatabase(IKp2aApp app, Database database, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher) : base(context, finish)
: base(app, operationFinishedHandler) {
{ _context = context;
_app = app; _app = app;
_database = database; }
_modificationWatcher = modificationWatcher;
}
public override void Run() public override void Run()
{ {
try try
{ {
IOConnectionInfo ioc = _database.Ioc; IOConnectionInfo ioc = _app.CurrentDb.Ioc;
IFileStorage fileStorage = _app.GetFileStorage(ioc); IFileStorage fileStorage = _app.GetFileStorage(ioc);
if (!(fileStorage is CachingFileStorage)) if (!(fileStorage is CachingFileStorage))
{ {
throw new Exception("Cannot sync a non-cached database!"); throw new Exception("Cannot sync a non-cached database!");
} }
StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase);
CachingFileStorage cachingFileStorage = (CachingFileStorage) fileStorage;
StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase); //download file from remote location and calculate hash:
CachingFileStorage cachingFileStorage = (CachingFileStorage)fileStorage; StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
string hash;
//download file from remote location and calculate hash: MemoryStream remoteData;
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile)); try
string hash; {
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
MemoryStream remoteData; Kp2aLog.Log("Checking for file change. Current hash = " + hash);
try }
{ catch (FileNotFoundException)
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash); {
Kp2aLog.Log("Checking for file change. Current hash = " + hash); StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
} cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
catch (FileNotFoundException) Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
{
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
cachingFileStorage.UpdateRemoteFile(ioc,
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
Kp2aLog.Log("Checking for file change: file not found"); Kp2aLog.Log("Checking for file change: file not found");
return; return;
} }
//check if remote file was modified: //check if remote file was modified:
var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc); var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc);
Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash); Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash);
if (baseVersionHash != hash) if (baseVersionHash != hash)
{ {
//remote file is modified //remote file is modified
if (cachingFileStorage.HasLocalChanges(ioc)) if (cachingFileStorage.HasLocalChanges(ioc))
{ {
//conflict! need to merge //conflict! need to merge
var _saveDb = new SaveDb(_app, new ActionOnOperationFinished(_app, _saveDb = new SaveDb(_context, _app, new ActionOnFinish(ActiveActivity, (success, result, activity) =>
(success, result, activity) => {
{ if (!success)
if (!success) {
{ Finish(false, result);
Finish(false, result); }
} else
else {
{ Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); }
} _saveDb = null;
}), _database, false, remoteData, _modificationWatcher); }), _app.CurrentDb, false, remoteData);
_saveDb.SetStatusLogger(StatusLogger); _saveDb.Run();
_saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
_saveDb.SyncInBackground = false;
_saveDb.Run();
_database.UpdateGlobals(); _app.CurrentDb.UpdateGlobals();
_app.MarkAllGroupsAsDirty(); _app.MarkAllGroupsAsDirty();
} }
else else
{ {
//only the remote file was modified -> reload database. //only the remote file was modified -> reload database.
var onFinished = new ActionOnOperationFinished(_app, (success, result, activity) => //note: it's best to lock the database and do a complete reload here (also better for UI consistency in case something goes wrong etc.)
{ _app.TriggerReload(_context, (bool result) => Finish(result));
if (!success) }
{ }
Finish(false, result); else
} {
else //remote file is unmodified
{ if (cachingFileStorage.HasLocalChanges(ioc))
new Handler(Looper.MainLooper).Post(() => {
{ //but we have local changes -> upload:
_database.UpdateGlobals(); StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
_app.MarkAllGroupsAsDirty(); StatusLogger.UpdateSubMessage("");
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
}); }
else
} {
}); //files are in sync: just set the result
var _loadDb = new LoadDb(_app, ioc, Task.FromResult(remoteData), Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
_database.KpDatabase.MasterKey, null, onFinished, true, false, _modificationWatcher); }
_loadDb.SetStatusLogger(StatusLogger); }
_loadDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message }
_loadDb.Run();
}
}
else
{
//remote file is unmodified
if (cachingFileStorage.HasLocalChanges(ioc))
{
//but we have local changes -> upload:
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
cachingFileStorage.UpdateRemoteFile(ioc,
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
StatusLogger.UpdateSubMessage("");
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
}
else
{
//files are in sync: just set the result
Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
}
}
}
catch (Java.Lang.InterruptedException e)
{
Kp2aLog.LogUnexpectedError(e);
//no Finish()
}
catch (Java.IO.InterruptedIOException e)
{
Kp2aLog.LogUnexpectedError(e);
//no Finish()
}
catch (Exception e) catch (Exception e)
{ {
Kp2aLog.LogUnexpectedError(e); Kp2aLog.LogUnexpectedError(e);
@@ -158,5 +115,10 @@ namespace keepass2android
} }
public void JoinWorkerThread()
{
if (_saveDb != null)
_saveDb.JoinWorkerThread();
}
} }
} }

View File

@@ -0,0 +1,57 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Android.App;
using Android.OS;
namespace keepass2android
{
public class ActionOnFinish: OnFinish
{
public delegate void ActionToPerformOnFinsh(bool success, String message, Activity activeActivity);
readonly ActionToPerformOnFinsh _actionToPerform;
public ActionOnFinish(Activity activity, ActionToPerformOnFinsh actionToPerform) : base(activity, null, null)
{
_actionToPerform = actionToPerform;
}
public ActionOnFinish(Activity activity, ActionToPerformOnFinsh actionToPerform, OnFinish finish) : base(activity, finish)
{
_actionToPerform = actionToPerform;
}
//if set to true, the previously active active will be passed to ActionToPerformOnFinish instead null if no activity is on foreground
public bool AllowInactiveActivity { get; set; }
public override void Run()
{
if (Message == null)
Message = "";
if (Handler != null)
{
Handler.Post(() => {_actionToPerform(Success, Message, ActiveActivity);});
}
else
_actionToPerform(Success, Message, AllowInactiveActivity ? (ActiveActivity ?? PreviouslyActiveActivity) : ActiveActivity);
base.Run();
}
}
}

View File

@@ -1,92 +0,0 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Android.App;
using Android.Content;
using Android.OS;
using keepass2android;
namespace keepass2android
{
public class ActionOnOperationFinished: OnOperationFinishedHandler
{
public delegate void ActionToPerformOnFinsh(bool success, String message, Context activeContext);
readonly ActionToPerformOnFinsh _actionToPerform;
public ActionOnOperationFinished(IKp2aApp app, ActionToPerformOnFinsh actionToPerform) : base(app, null, null)
{
_actionToPerform = actionToPerform;
}
public ActionOnOperationFinished(IKp2aApp app, ActionToPerformOnFinsh actionToPerform, OnOperationFinishedHandler operationFinishedHandler) : base(app, operationFinishedHandler)
{
_actionToPerform = actionToPerform;
}
public override void Run()
{
if (Message == null)
Message = "";
if (Handler != null)
{
Handler.Post(() =>
{
_actionToPerform(Success, Message, ActiveContext);
});
}
else
{
_actionToPerform(Success, Message, ActiveContext);
}
base.Run();
}
}
}
//Action which runs when the contextInstanceId is the active context
// otherwise it is registered as pending action for the context instance.
public class ActionInContextInstanceOnOperationFinished : ActionOnOperationFinished
{
private readonly int _contextInstanceId;
private IKp2aApp _app;
public ActionInContextInstanceOnOperationFinished(int contextInstanceId, IKp2aApp app, ActionToPerformOnFinsh actionToPerform) : base(app, actionToPerform)
{
_contextInstanceId = contextInstanceId;
_app = app;
}
public ActionInContextInstanceOnOperationFinished(int contextInstanceId, IKp2aApp app, ActionToPerformOnFinsh actionToPerform, OnOperationFinishedHandler operationFinishedHandler) : base(app, actionToPerform, operationFinishedHandler)
{
_contextInstanceId = contextInstanceId;
_app = app;
}
public override void Run()
{
if ((ActiveContext as IContextInstanceIdProvider)?.ContextInstanceId != _contextInstanceId)
{
_app.RegisterPendingActionForContextInstance(_contextInstanceId, this);
}
else _app.UiThreadHandler.Post(() => base.Run());
}
}

View File

@@ -21,7 +21,7 @@ using KeePassLib;
namespace keepass2android namespace keepass2android
{ {
public class AddEntry : OperationWithFinishHandler { public class AddEntry : RunnableOnFinish {
protected Database Db protected Database Db
{ {
get { return _app.CurrentDb; } get { return _app.CurrentDb; }
@@ -30,20 +30,22 @@ namespace keepass2android
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly PwEntry _entry; private readonly PwEntry _entry;
private readonly PwGroup _parentGroup; private readonly PwGroup _parentGroup;
private readonly Activity _ctx;
private readonly Database _db; private readonly Database _db;
public static AddEntry GetInstance(IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnOperationFinishedHandler operationFinishedHandler, Database db) { public static AddEntry GetInstance(Activity ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish, Database db) {
return new AddEntry(db, app, entry, parentGroup, operationFinishedHandler); return new AddEntry(ctx, db, app, entry, parentGroup, finish);
} }
public AddEntry(Database db, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) { public AddEntry(Activity ctx, Database db, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(ctx, finish) {
_ctx = ctx;
_db = db; _db = db;
_parentGroup = parentGroup; _parentGroup = parentGroup;
_app = app; _app = app;
_entry = entry; _entry = entry;
_operationFinishedHandler = new AfterAdd(app.CurrentDb, entry, app,operationFinishedHandler); _onFinishToRun = new AfterAdd(ctx, app.CurrentDb, entry, app,OnFinishToRun);
} }
@@ -63,17 +65,17 @@ namespace keepass2android
_db.Elements.Add(_entry); _db.Elements.Add(_entry);
// Commit to disk // Commit to disk
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler); SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun);
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
private class AfterAdd : OnOperationFinishedHandler { private class AfterAdd : OnFinish {
private readonly Database _db; private readonly Database _db;
private readonly PwEntry _entry; private readonly PwEntry _entry;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
public AfterAdd( Database db, PwEntry entry, IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) { public AfterAdd(Activity activity, Database db, PwEntry entry, IKp2aApp app, OnFinish finish):base(activity, finish) {
_db = db; _db = db;
_entry = entry; _entry = entry;
_app = app; _app = app;

View File

@@ -23,7 +23,7 @@ using KeePassLib;
namespace keepass2android namespace keepass2android
{ {
public class AddGroup : OperationWithFinishHandler { public class AddGroup : RunnableOnFinish {
internal Database Db internal Database Db
{ {
get { return _app.CurrentDb; } get { return _app.CurrentDb; }
@@ -38,16 +38,18 @@ namespace keepass2android
public PwGroup Group; public PwGroup Group;
internal PwGroup Parent; internal PwGroup Parent;
protected bool DontSave; protected bool DontSave;
readonly Activity _ctx;
public static AddGroup GetInstance(IKp2aApp app, string name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnOperationFinishedHandler operationFinishedHandler, bool dontSave) { public static AddGroup GetInstance(Activity ctx, IKp2aApp app, string name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnFinish finish, bool dontSave) {
return new AddGroup(app, name, iconid, groupCustomIconId, parent, operationFinishedHandler, dontSave); return new AddGroup(ctx, app, name, iconid, groupCustomIconId, parent, finish, dontSave);
} }
private AddGroup(IKp2aApp app, String name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnOperationFinishedHandler operationFinishedHandler, bool dontSave) private AddGroup(Activity ctx, IKp2aApp app, String name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnFinish finish, bool dontSave)
: base(app, operationFinishedHandler) : base(ctx, finish)
{ {
_ctx = ctx;
_name = name; _name = name;
_iconId = iconid; _iconId = iconid;
_groupCustomIconId = groupCustomIconId; _groupCustomIconId = groupCustomIconId;
@@ -55,7 +57,7 @@ namespace keepass2android
DontSave = dontSave; DontSave = dontSave;
_app = app; _app = app;
_operationFinishedHandler = new AfterAdd(_app, this, operationFinishedHandler); _onFinishToRun = new AfterAdd(ctx, this, OnFinishToRun);
} }
@@ -72,15 +74,15 @@ namespace keepass2android
_app.CurrentDb.Elements.Add(Group); _app.CurrentDb.Elements.Add(Group);
// Commit to disk // Commit to disk
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler, DontSave, null); SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun, DontSave);
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
private class AfterAdd : OnOperationFinishedHandler { private class AfterAdd : OnFinish {
readonly AddGroup _addGroup; readonly AddGroup _addGroup;
public AfterAdd(IKp2aApp app, AddGroup addGroup,OnOperationFinishedHandler operationFinishedHandler): base(app, operationFinishedHandler) { public AfterAdd(Activity activity, AddGroup addGroup,OnFinish finish): base(activity, finish) {
_addGroup = addGroup; _addGroup = addGroup;
} }

View File

@@ -26,7 +26,7 @@ using KeePassLib.Utility;
namespace keepass2android namespace keepass2android
{ {
public class AddTemplateEntries : OperationWithFinishHandler { public class AddTemplateEntries : RunnableOnFinish {
public class TemplateEntry public class TemplateEntry
{ {
@@ -130,13 +130,15 @@ namespace keepass2android
} }
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly Activity _ctx;
public AddTemplateEntries(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler) public AddTemplateEntries(Activity ctx, IKp2aApp app, OnFinish finish)
: base(app, operationFinishedHandler) : base(ctx, finish)
{ {
_app = app; _ctx = ctx;
_app = app;
//_operationFinishedHandler = new AfterAdd(this, operationFinishedHandler); //_onFinishToRun = new AfterAdd(this, OnFinishToRun);
} }
public static readonly List<TemplateEntry> TemplateEntries = new List<TemplateEntry>() public static readonly List<TemplateEntry> TemplateEntries = new List<TemplateEntry>()
@@ -311,7 +313,7 @@ namespace keepass2android
_app.DirtyGroups.Add(templateGroup); _app.DirtyGroups.Add(templateGroup);
// Commit to disk // Commit to disk
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler); SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun);
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
@@ -335,6 +337,7 @@ namespace keepass2android
_app.DirtyGroups.Add(_app.CurrentDb.KpDatabase.RootGroup); _app.DirtyGroups.Add(_app.CurrentDb.KpDatabase.RootGroup);
_app.CurrentDb.GroupsById[templateGroup.Uuid] = templateGroup; _app.CurrentDb.GroupsById[templateGroup.Uuid] = templateGroup;
_app.CurrentDb.Elements.Add(templateGroup); _app.CurrentDb.Elements.Add(templateGroup);
} }
addedEntries = new List<PwEntry>(); addedEntries = new List<PwEntry>();
@@ -366,11 +369,11 @@ namespace keepass2android
return entry; return entry;
} }
private class AfterAdd : OnOperationFinishedHandler { private class AfterAdd : OnFinish {
private readonly Database _db; private readonly Database _db;
private readonly List<PwEntry> _entries; private readonly List<PwEntry> _entries;
public AfterAdd(IKp2aApp app, Database db, List<PwEntry> entries, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) { public AfterAdd(Activity activity, Database db, List<PwEntry> entries, OnFinish finish):base(activity, finish) {
_db = db; _db = db;
_entries = entries; _entries = entries;

View File

@@ -16,8 +16,8 @@ namespace keepass2android.database.edit
{ {
public class CopyEntry: AddEntry public class CopyEntry: AddEntry
{ {
public CopyEntry(IKp2aApp app, PwEntry entry, OnOperationFinishedHandler operationFinishedHandler, Database db) public CopyEntry(Activity ctx, IKp2aApp app, PwEntry entry, OnFinish finish, Database db)
: base(db, app, CreateCopy(entry, app), entry.ParentGroup, operationFinishedHandler) : base(ctx, db, app, CreateCopy(entry, app), entry.ParentGroup, finish)
{ {
} }

View File

@@ -26,24 +26,27 @@ using KeePassLib.Keys;
namespace keepass2android namespace keepass2android
{ {
public class CreateDb : OperationWithFinishHandler { public class CreateDb : RunnableOnFinish {
private readonly IOConnectionInfo _ioc; private readonly IOConnectionInfo _ioc;
private readonly bool _dontSave; private readonly bool _dontSave;
private readonly Activity _ctx;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private CompositeKey _key; private CompositeKey _key;
private readonly bool _makeCurrent; private readonly bool _makeCurrent;
public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnOperationFinishedHandler operationFinishedHandler, bool dontSave, bool makeCurrent): base(app, operationFinishedHandler) { public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, bool makeCurrent): base(ctx, finish) {
_ioc = ioc; _ctx = ctx;
_ioc = ioc;
_dontSave = dontSave; _dontSave = dontSave;
_makeCurrent = makeCurrent; _makeCurrent = makeCurrent;
_app = app; _app = app;
} }
public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnOperationFinishedHandler operationFinishedHandler, bool dontSave, CompositeKey key, bool makeCurrent) public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, CompositeKey key, bool makeCurrent)
: base(app, operationFinishedHandler) : base(ctx, finish)
{ {
_ioc = ioc; _ctx = ctx;
_ioc = ioc;
_dontSave = dontSave; _dontSave = dontSave;
_app = app; _app = app;
_key = key; _key = key;
@@ -74,19 +77,19 @@ namespace keepass2android
db.SearchHelper = new SearchDbHelper(_app); db.SearchHelper = new SearchDbHelper(_app);
// Add a couple default groups // Add a couple default groups
AddGroup internet = AddGroup.GetInstance(_app, "Internet", 1, null, db.KpDatabase.RootGroup, null, true); AddGroup internet = AddGroup.GetInstance(_ctx, _app, "Internet", 1, null, db.KpDatabase.RootGroup, null, true);
internet.Run(); internet.Run();
AddGroup email = AddGroup.GetInstance(_app, "eMail", 19, null, db.KpDatabase.RootGroup, null, true); AddGroup email = AddGroup.GetInstance(_ctx, _app, "eMail", 19, null, db.KpDatabase.RootGroup, null, true);
email.Run(); email.Run();
List<PwEntry> addedEntries; List<PwEntry> addedEntries;
AddTemplateEntries addTemplates = new AddTemplateEntries(_app, null); AddTemplateEntries addTemplates = new AddTemplateEntries(_ctx, _app, null);
addTemplates.AddTemplates(out addedEntries); addTemplates.AddTemplates(out addedEntries);
// Commit changes // Commit changes
SaveDb save = new SaveDb(_app, db, operationFinishedHandler, _dontSave, null); SaveDb save = new SaveDb(_ctx, _app, db, OnFinishToRun, _dontSave);
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);
_operationFinishedHandler = null; _onFinishToRun = null;
save.Run(); save.Run();
db.UpdateGlobals(); db.UpdateGlobals();

View File

@@ -1,39 +0,0 @@
using Java.Lang;
namespace keepass2android;
public interface IDatabaseModificationWatcher
{
void BeforeModifyDatabases();
void AfterModifyDatabases();
}
public class NullDatabaseModificationWatcher : IDatabaseModificationWatcher
{
public void BeforeModifyDatabases() { }
public void AfterModifyDatabases() { }
}
public class BackgroundDatabaseModificationLocker(IKp2aApp app) : IDatabaseModificationWatcher
{
public void BeforeModifyDatabases()
{
while (true)
{
if (app.DatabasesBackgroundModificationLock.TryEnterWriteLock(TimeSpan.FromSeconds(0.1)))
{
break;
}
if (Java.Lang.Thread.Interrupted())
{
throw new InterruptedException();
}
}
}
public void AfterModifyDatabases()
{
app.DatabasesBackgroundModificationLock.ExitWriteLock();
}
}

View File

@@ -29,8 +29,8 @@ namespace keepass2android
private readonly PwEntry _entry; private readonly PwEntry _entry;
private UiStringKey _statusMessage; private UiStringKey _statusMessage;
public DeleteEntry(IKp2aApp app, PwEntry entry, OnOperationFinishedHandler operationFinishedHandler):base(operationFinishedHandler, app) { public DeleteEntry(Activity activiy, IKp2aApp app, PwEntry entry, OnFinish finish):base(activiy, finish, app) {
Ctx = activiy;
Db = app.FindDatabaseForElement(entry); Db = app.FindDatabaseForElement(entry);
_entry = entry; _entry = entry;

View File

@@ -29,25 +29,25 @@ namespace keepass2android
private PwGroup _group; private PwGroup _group;
protected bool DontSave; protected bool DontSave;
public DeleteGroup(Activity activity, IKp2aApp app, PwGroup group, OnOperationFinishedHandler operationFinishedHandler) public DeleteGroup(Activity activity, IKp2aApp app, PwGroup group, OnFinish finish)
: base(operationFinishedHandler, app) : base(activity, finish, app)
{ {
SetMembers(app, group, false); SetMembers(activity, app, group, false);
} }
/* /*
public DeleteGroup(Context ctx, Database db, PwGroup group, Activity act, OnOperationFinishedHandler operationFinishedHandler, bool dontSave) public DeleteGroup(Context ctx, Database db, PwGroup group, Activity act, OnFinish finish, bool dontSave)
: base(operationFinishedHandler) : base(finish)
{ {
SetMembers(ctx, db, group, act, dontSave); SetMembers(ctx, db, group, act, dontSave);
} }
public DeleteGroup(Context ctx, Database db, PwGroup group, OnOperationFinishedHandler operationFinishedHandler, bool dontSave):base(operationFinishedHandler) { public DeleteGroup(Context ctx, Database db, PwGroup group, OnFinish finish, bool dontSave):base(finish) {
SetMembers(ctx, db, group, null, dontSave); SetMembers(ctx, db, group, null, dontSave);
} }
*/ */
private void SetMembers(IKp2aApp app, PwGroup group, bool dontSave) private void SetMembers(Activity activity, IKp2aApp app, PwGroup group, bool dontSave)
{ {
base.SetMembers(app.FindDatabaseForElement(group)); base.SetMembers(activity, app.FindDatabaseForElement(group));
_group = group; _group = group;
DontSave = dontSave; DontSave = dontSave;

View File

@@ -12,11 +12,11 @@ namespace keepass2android
private readonly List<IStructureItem> _elementsToDelete; private readonly List<IStructureItem> _elementsToDelete;
private readonly bool _canRecycle; private readonly bool _canRecycle;
public DeleteMultipleItemsFromOneDatabase(Activity activity, Database db, List<IStructureItem> elementsToDelete, OnOperationFinishedHandler operationFinishedHandler, IKp2aApp app) public DeleteMultipleItemsFromOneDatabase(Activity activity, Database db, List<IStructureItem> elementsToDelete, OnFinish finish, IKp2aApp app)
: base(operationFinishedHandler, app) : base(activity, finish, app)
{ {
_elementsToDelete = elementsToDelete; _elementsToDelete = elementsToDelete;
SetMembers(db); SetMembers(activity, db);
//determine once. The property is queried for each delete operation, but might return false //determine once. The property is queried for each delete operation, but might return false
//after one entry/group is deleted (and thus in recycle bin and thus can't be recycled anymore) //after one entry/group is deleted (and thus in recycle bin and thus can't be recycled anymore)

View File

@@ -6,10 +6,10 @@ using KeePassLib;
namespace keepass2android namespace keepass2android
{ {
public abstract class DeleteRunnable : OperationWithFinishHandler public abstract class DeleteRunnable : RunnableOnFinish
{ {
protected DeleteRunnable(OnOperationFinishedHandler operationFinishedHandler, IKp2aApp app) protected DeleteRunnable(Activity activity, OnFinish finish, IKp2aApp app)
: base(app, operationFinishedHandler) : base(activity, finish)
{ {
App = app; App = app;
} }
@@ -18,10 +18,11 @@ namespace keepass2android
protected Database Db; protected Database Db;
protected Activity Ctx;
protected void SetMembers(Activity activity, Database db)
protected void SetMembers( Database db)
{ {
Ctx = activity;
Db = db; Db = db;
} }
@@ -130,18 +131,18 @@ namespace keepass2android
(dlgSender, dlgEvt) => (dlgSender, dlgEvt) =>
{ {
DeletePermanently = true; DeletePermanently = true;
BlockingOperationStarter pt = new BlockingOperationStarter(App, this); ProgressTask pt = new ProgressTask(App, Ctx, this);
pt.Run(); pt.Run();
}, },
(dlgSender, dlgEvt) => (dlgSender, dlgEvt) =>
{ {
DeletePermanently = false; DeletePermanently = false;
BlockingOperationStarter pt = new BlockingOperationStarter(App, this); ProgressTask pt = new ProgressTask(App, Ctx, this);
pt.Run(); pt.Run();
}, },
(dlgSender, dlgEvt) => { }, (dlgSender, dlgEvt) => { },
messageSuffix); Ctx, messageSuffix);
@@ -152,12 +153,12 @@ namespace keepass2android
QuestionNoRecycleResourceId, QuestionNoRecycleResourceId,
(dlgSender, dlgEvt) => (dlgSender, dlgEvt) =>
{ {
BlockingOperationStarter pt = new BlockingOperationStarter(App, this); ProgressTask pt = new ProgressTask(App, Ctx, this);
pt.Run(); pt.Run();
}, },
null, null,
(dlgSender, dlgEvt) => { }, (dlgSender, dlgEvt) => { },
messageSuffix); Ctx, messageSuffix);
} }
@@ -214,7 +215,7 @@ namespace keepass2android
Android.Util.Log.Debug("KP2A", "Calling PerformDelete.."); Android.Util.Log.Debug("KP2A", "Calling PerformDelete..");
PerformDelete(touchedGroups, permanentlyDeletedGroups); PerformDelete(touchedGroups, permanentlyDeletedGroups);
_operationFinishedHandler = new ActionOnOperationFinished(App,(success, message, context) => _onFinishToRun = new ActionOnFinish(ActiveActivity,(success, message, activity) =>
{ {
if (success) if (success)
{ {
@@ -235,10 +236,10 @@ namespace keepass2android
// Let's not bother recovering from a failure to save. It is too much work. // Let's not bother recovering from a failure to save. It is too much work.
App.Lock(false, false); App.Lock(false, false);
} }
}, operationFinishedHandler); }, OnFinishToRun);
// Commit database // Commit database
SaveDb save = new SaveDb( App, Db, operationFinishedHandler, false, null); SaveDb save = new SaveDb(Ctx, App, Db, OnFinishToRun, false);
save.ShowDatabaseIocInStatus = ShowDatabaseIocInStatus; save.ShowDatabaseIocInStatus = ShowDatabaseIocInStatus;
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);

View File

@@ -23,7 +23,7 @@ using KeePassLib;
namespace keepass2android namespace keepass2android
{ {
public class EditGroup : OperationWithFinishHandler { public class EditGroup : RunnableOnFinish {
internal Database Db internal Database Db
{ {
get { return _app.FindDatabaseForElement(Group); } get { return _app.FindDatabaseForElement(Group); }
@@ -36,17 +36,19 @@ namespace keepass2android
private readonly PwIcon _iconId; private readonly PwIcon _iconId;
private readonly PwUuid _customIconId; private readonly PwUuid _customIconId;
internal PwGroup Group; internal PwGroup Group;
readonly Activity _ctx;
public EditGroup(IKp2aApp app, String name, PwIcon iconid, PwUuid customIconId, PwGroup group, OnOperationFinishedHandler operationFinishedHandler) public EditGroup(Activity ctx, IKp2aApp app, String name, PwIcon iconid, PwUuid customIconId, PwGroup group, OnFinish finish)
: base(app, operationFinishedHandler) : base(ctx, finish)
{ {
_ctx = ctx;
_name = name; _name = name;
_iconId = iconid; _iconId = iconid;
Group = group; Group = group;
_customIconId = customIconId; _customIconId = customIconId;
_app = app; _app = app;
_operationFinishedHandler = new AfterEdit(app, this, operationFinishedHandler); _onFinishToRun = new AfterEdit(ctx, this, OnFinishToRun);
} }
@@ -58,16 +60,16 @@ namespace keepass2android
Group.Touch(true); Group.Touch(true);
// Commit to disk // Commit to disk
SaveDb save = new SaveDb(_app, Db, operationFinishedHandler); SaveDb save = new SaveDb(_ctx, _app, Db, OnFinishToRun);
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
private class AfterEdit : OnOperationFinishedHandler { private class AfterEdit : OnFinish {
readonly EditGroup _editGroup; readonly EditGroup _editGroup;
public AfterEdit(IKp2aApp app, EditGroup editGroup, OnOperationFinishedHandler operationFinishedHandler) public AfterEdit(Activity ctx, EditGroup editGroup, OnFinish finish)
: base(app, operationFinishedHandler) : base(ctx, finish)
{ {
_editGroup = editGroup; _editGroup = editGroup;
} }

View File

@@ -21,10 +21,10 @@ using Android.App;
namespace keepass2android namespace keepass2android
{ {
public abstract class FileOnFinish : OnOperationFinishedHandler { public abstract class FileOnFinish : OnFinish {
private String _filename = ""; private String _filename = "";
protected FileOnFinish(IKp2aApp app, FileOnFinish operationFinishedHandler):base(app, operationFinishedHandler) { protected FileOnFinish(Activity activity, FileOnFinish finish):base(activity, finish) {
} }
public string Filename public string Filename

View File

@@ -1,13 +0,0 @@
namespace keepass2android;
// A context instance can be the instance of an Activity. Even if the activity is recreated (due to a configuration change, for example), the instance id must remain the same
// but it must be different for other activities/services or if the activity is finished and then starts again.
// We want to be able to perform actions on a context instance, even though that instance might not live at the time when we want to perform the action.
// In that case, we want to be able to register the action such that it is performed when the activity is recreated.
public interface IContextInstanceIdProvider
{
int ContextInstanceId { get; }
}

View File

@@ -21,88 +21,60 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.App; using Android.App;
using Android.OS;
using KeePass.Util; using KeePass.Util;
using keepass2android.database.edit; using keepass2android.database.edit;
using keepass2android.Io;
using KeePassLib; using KeePassLib;
using KeePassLib.Keys; using KeePassLib.Keys;
using KeePassLib.Serialization; using KeePassLib.Serialization;
namespace keepass2android namespace keepass2android
{ {
public class LoadDb : OperationWithFinishHandler { public class LoadDb : RunnableOnFinish {
private readonly IOConnectionInfo _ioc; private readonly IOConnectionInfo _ioc;
private readonly Task<MemoryStream> _databaseData; private readonly Task<MemoryStream> _databaseData;
private readonly CompositeKey _compositeKey; private readonly CompositeKey _compositeKey;
private readonly string? _keyfileOrProvider; private readonly string _keyfileOrProvider;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly bool _rememberKeyfile; private readonly bool _rememberKeyfile;
IDatabaseFormat _format; IDatabaseFormat _format;
public bool DoNotSetStatusLoggerMessage = false; public LoadDb(Activity activity, IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnFinish finish, bool updateLastUsageTimestamp, bool makeCurrent): base(activity, finish)
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey,
string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler,
bool updateLastUsageTimestamp, bool makeCurrent, IDatabaseModificationWatcher modificationWatcher = null): base(app, operationFinishedHandler)
{ {
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher(); _app = app;
_app = app;
_ioc = ioc; _ioc = ioc;
_databaseData = databaseData; _databaseData = databaseData;
_compositeKey = compositeKey; _compositeKey = compositeKey;
_keyfileOrProvider = keyfileOrProvider; _keyfileOrProvider = keyfileOrProvider;
_updateLastUsageTimestamp = updateLastUsageTimestamp; _updateLastUsageTimestamp = updateLastUsageTimestamp;
_makeCurrent = makeCurrent; _makeCurrent = makeCurrent;
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile); _rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
} }
protected bool success = false; protected bool success = false;
private bool _updateLastUsageTimestamp; private bool _updateLastUsageTimestamp;
private readonly bool _makeCurrent; private readonly bool _makeCurrent;
private readonly IDatabaseModificationWatcher _modificationWatcher;
public override void Run() public override void Run()
{ {
try try
{ {
try try
{ {
//make sure the file data is stored in the recent files list even if loading fails //make sure the file data is stored in the recent files list even if loading fails
SaveFileData(_ioc, _keyfileOrProvider); SaveFileData(_ioc, _keyfileOrProvider);
var fileStorage = _app.GetFileStorage(_ioc); StatusLogger.UpdateMessage(UiStringKey.loading_database);
RequiresSubsequentSync = false;
if (!DoNotSetStatusLoggerMessage)
{
StatusLogger.UpdateMessage(UiStringKey.loading_database);
}
//get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not: //get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not:
MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result; MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
MemoryStream databaseStream; MemoryStream databaseStream;
if (preloadedMemoryStream != null) if (preloadedMemoryStream != null)
{ databaseStream = preloadedMemoryStream;
//note: if the stream has been loaded already, we don't need to trigger another sync later on else
databaseStream = preloadedMemoryStream;
}
else
{ {
if (_app.SyncInBackgroundPreference && fileStorage is CachingFileStorage cachingFileStorage && using (Stream s = _app.GetFileStorage(_ioc).OpenFileForRead(_ioc))
cachingFileStorage.IsCached(_ioc))
{
cachingFileStorage.IsOffline = true;
//no warning. We'll trigger a sync later.
cachingFileStorage.TriggerWarningWhenFallingBackToCache = false;
RequiresSubsequentSync = true;
}
using (Stream s = fileStorage.OpenFileForRead(_ioc))
{ {
databaseStream = new MemoryStream(); databaseStream = new MemoryStream();
s.CopyTo(databaseStream); s.CopyTo(databaseStream);
@@ -110,13 +82,8 @@ namespace keepass2android
} }
} }
if (!StatusLogger.ContinueWork())
{
return;
}
//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess: //ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
_format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(fileStorage.GetFileExtension(_ioc))); _format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(_app.GetFileStorage(_ioc).GetFileExtension(_ioc)));
TryLoad(databaseStream); TryLoad(databaseStream);
@@ -153,13 +120,7 @@ namespace keepass2android
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception); Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
return; return;
} }
catch (Java.Lang.InterruptedException) catch (Exception e)
{
Kp2aLog.Log("Load interrupted");
//close without Finish()
return;
}
catch (Exception e)
{ {
if (!(e is InvalidCompositeKeyException)) if (!(e is InvalidCompositeKeyException))
Kp2aLog.LogUnexpectedError(e); Kp2aLog.LogUnexpectedError(e);
@@ -170,15 +131,14 @@ namespace keepass2android
} }
public bool RequiresSubsequentSync { get; set; } = false; /// <summary>
/// <summary>
/// Holds the exception which was thrown during execution (if any) /// Holds the exception which was thrown during execution (if any)
/// </summary> /// </summary>
public Exception Exception { get; set; } public Exception Exception { get; set; }
Database TryLoad(MemoryStream databaseStream) Database TryLoad(MemoryStream databaseStream)
{ {
Kp2aLog.Log("LoadDb: Copying database in memory");
//create a copy of the stream so we can try again if we get an exception which indicates we should change parameters //create a copy of the stream so we can try again if we get an exception which indicates we should change parameters
//This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors. //This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors.
//Alternatives would involve increased traffic (if file is on remote) and slower loading times, so this seems to be the best choice. //Alternatives would involve increased traffic (if file is on remote) and slower loading times, so this seems to be the best choice.
@@ -187,21 +147,16 @@ namespace keepass2android
workingCopy.Seek(0, SeekOrigin.Begin); workingCopy.Seek(0, SeekOrigin.Begin);
//reset stream if we need to reuse it later: //reset stream if we need to reuse it later:
databaseStream.Seek(0, SeekOrigin.Begin); databaseStream.Seek(0, SeekOrigin.Begin);
if (!StatusLogger.ContinueWork()) Kp2aLog.Log("LoadDb: Ready to start loading");
{ //now let's go:
throw new Java.Lang.InterruptedException();
}
//now let's go:
try try
{ {
Database newDb = Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent);
_app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent, _modificationWatcher); Kp2aLog.Log("LoadDB OK");
Kp2aLog.Log("LoadDB OK");
Finish(true, _format.SuccessMessage); Finish(true, _format.SuccessMessage);
return newDb; return newDb;
} }
catch (OldFormatException) catch (OldFormatException)
{ {
_format = new KdbDatabaseFormat(_app); _format = new KdbDatabaseFormat(_app);

View File

@@ -10,16 +10,18 @@ using KeePassLib.Interfaces;
namespace keepass2android.database.edit namespace keepass2android.database.edit
{ {
public class MoveElements: OperationWithFinishHandler public class MoveElements: RunnableOnFinish
{ {
private readonly List<IStructureItem> _elementsToMove; private readonly List<IStructureItem> _elementsToMove;
private readonly PwGroup _targetGroup; private readonly PwGroup _targetGroup;
private readonly IKp2aApp _app; private readonly Activity _ctx;
private readonly IKp2aApp _app;
public MoveElements(List<IStructureItem> elementsToMove, PwGroup targetGroup,IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler) : base(app, operationFinishedHandler) public MoveElements(List<IStructureItem> elementsToMove, PwGroup targetGroup, Activity ctx, IKp2aApp app, OnFinish finish) : base(ctx, finish)
{ {
_elementsToMove = elementsToMove; _elementsToMove = elementsToMove;
_targetGroup = targetGroup; _targetGroup = targetGroup;
_ctx = ctx;
_app = app; _app = app;
} }
@@ -121,24 +123,24 @@ namespace keepass2android.database.edit
int indexToSave = 0; int indexToSave = 0;
bool allSavesSuccess = true; bool allSavesSuccess = true;
void ContinueSave(bool success, string message, Context activeActivity) void ContinueSave(bool success, string message, Activity activeActivity)
{ {
allSavesSuccess &= success; allSavesSuccess &= success;
indexToSave++; indexToSave++;
if (indexToSave == allDatabasesToSave.Count) if (indexToSave == allDatabasesToSave.Count)
{ {
operationFinishedHandler.SetResult(allSavesSuccess); OnFinishToRun.SetResult(allSavesSuccess);
operationFinishedHandler.Run(); OnFinishToRun.Run();
return; return;
} }
SaveDb saveDb = new SaveDb( _app, allDatabasesToSave[indexToSave], new ActionOnOperationFinished(_app, ContinueSave), false, null); SaveDb saveDb = new SaveDb(_ctx, _app, allDatabasesToSave[indexToSave], new ActionOnFinish(activeActivity, ContinueSave), false);
saveDb.SetStatusLogger(StatusLogger); saveDb.SetStatusLogger(StatusLogger);
saveDb.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1; saveDb.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1;
saveDb.Run(); saveDb.Run();
} }
SaveDb save = new SaveDb(_app, allDatabasesToSave[0], new ActionOnOperationFinished(_app, ContinueSave), false, null); SaveDb save = new SaveDb(_ctx, _app, allDatabasesToSave[0], new ActionOnFinish(ActiveActivity, ContinueSave), false);
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);
save.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1; save.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1;
save.Run(); save.Run();

View File

@@ -22,16 +22,10 @@ using Android.Content;
using Android.OS; using Android.OS;
using Android.Widget; using Android.Widget;
using Google.Android.Material.Dialog; using Google.Android.Material.Dialog;
using KeePassLib.Interfaces;
namespace keepass2android namespace keepass2android
{ {
public interface IActiveContextProvider public abstract class OnFinish
{
Context ActiveContext { get; }
}
public abstract class OnOperationFinishedHandler
{ {
protected bool Success; protected bool Success;
protected String Message; protected String Message;
@@ -43,41 +37,63 @@ namespace keepass2android
set; set;
} }
protected Context ActiveContext protected OnFinish BaseOnFinish;
{
get
{
return _activeContextProvider?.ActiveContext;
}
}
protected OnOperationFinishedHandler NextOnOperationFinishedHandler;
protected Handler Handler; protected Handler Handler;
private IKp2aStatusLogger _statusLogger = new Kp2aNullStatusLogger(); //default: no logging but not null -> can be used whenever desired private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired
private readonly IActiveContextProvider _activeContextProvider; private Activity _activeActivity, _previouslyActiveActivity;
public IKp2aStatusLogger StatusLogger
public ProgressDialogStatusLogger StatusLogger
{ {
get { return _statusLogger; } get { return _statusLogger; }
set { _statusLogger = value; } set { _statusLogger = value; }
} protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, Handler handler)
{
_activeContextProvider = activeContextProvider;
NextOnOperationFinishedHandler = null;
Handler = handler;
} }
protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, OnOperationFinishedHandler operationFinishedHandler, Handler handler) public Activity ActiveActivity
{
get { return _activeActivity; }
set
{
if (_activeActivity != null && _activeActivity != _previouslyActiveActivity)
{
_previouslyActiveActivity = _activeActivity;
}
_activeActivity = value;
if (BaseOnFinish != null)
{
BaseOnFinish.ActiveActivity = value;
}
}
}
public Activity PreviouslyActiveActivity
{
get { return _previouslyActiveActivity; }
}
protected OnFinish(Activity activeActivity, Handler handler)
{
ActiveActivity = activeActivity;
BaseOnFinish = null;
Handler = handler;
}
protected OnFinish(Activity activeActivity, OnFinish finish, Handler handler)
{ {
_activeContextProvider = activeContextProvider; ActiveActivity = activeActivity;
NextOnOperationFinishedHandler = operationFinishedHandler; BaseOnFinish = finish;
Handler = handler; Handler = handler;
} }
protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, OnOperationFinishedHandler operationFinishedHandler) protected OnFinish(Activity activeActivity, OnFinish finish)
{ {
_activeContextProvider = activeContextProvider; ActiveActivity = activeActivity;
NextOnOperationFinishedHandler = operationFinishedHandler; BaseOnFinish = finish;
Handler = null; Handler = null;
} }
@@ -94,19 +110,14 @@ namespace keepass2android
} }
public virtual void Run() { public virtual void Run() {
if (NextOnOperationFinishedHandler == null) return; if (BaseOnFinish == null) return;
// Pass on result on call finish // Pass on result on call finish
NextOnOperationFinishedHandler.SetResult(Success, Message, ImportantMessage, Exception); BaseOnFinish.SetResult(Success, Message, ImportantMessage, Exception);
var handler = Handler ?? NextOnOperationFinishedHandler.Handler ?? null; if ( Handler != null ) {
Handler.Post(BaseOnFinish.Run);
if (handler != null ) {
handler.Post(() =>
{
NextOnOperationFinishedHandler.Run();
});
} else { } else {
NextOnOperationFinishedHandler.Run(); BaseOnFinish.Run();
} }
} }
@@ -117,7 +128,7 @@ namespace keepass2android
public static void DisplayMessage(Context ctx, string message, bool makeDialog) public static void DisplayMessage(Context ctx, string message, bool makeDialog)
{ {
if ( !String.IsNullOrEmpty(message) ) { if ( !String.IsNullOrEmpty(message) ) {
Kp2aLog.Log("OnOperationFinishedHandler message: " + message); Kp2aLog.Log("OnFinish message: " + message);
if (makeDialog && ctx != null) if (makeDialog && ctx != null)
{ {
try try
@@ -141,4 +152,3 @@ namespace keepass2android
} }
} }

View File

@@ -1,69 +0,0 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Android.App;
using Android.Content;
using KeePassLib.Interfaces;
namespace keepass2android
{
public abstract class OperationWithFinishHandler {
protected OnOperationFinishedHandler _operationFinishedHandler;
public IKp2aStatusLogger StatusLogger = new Kp2aNullStatusLogger(); //default: empty but not null
private IActiveContextProvider _activeContextProvider;
protected OperationWithFinishHandler(IActiveContextProvider activeContextProvider, OnOperationFinishedHandler operationFinishedHandler)
{
_activeContextProvider = activeContextProvider;
_operationFinishedHandler = operationFinishedHandler;
}
public OnOperationFinishedHandler operationFinishedHandler
{
get { return _operationFinishedHandler; }
set { _operationFinishedHandler = value; }
}
protected void Finish(bool result, String message, bool importantMessage = false, Exception exception = null) {
if ( operationFinishedHandler != null ) {
operationFinishedHandler.SetResult(result, message, importantMessage, exception);
operationFinishedHandler.Run();
}
}
protected void Finish(bool result) {
if ( operationFinishedHandler != null ) {
operationFinishedHandler.SetResult(result);
operationFinishedHandler.Run();
}
}
public void SetStatusLogger(IKp2aStatusLogger statusLogger) {
if (operationFinishedHandler != null)
{
operationFinishedHandler.StatusLogger = statusLogger;
}
StatusLogger = statusLogger;
}
public abstract void Run();
}
}

View File

@@ -0,0 +1,78 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Android.App;
using Android.Content;
namespace keepass2android
{
public abstract class RunnableOnFinish {
protected OnFinish _onFinishToRun;
public ProgressDialogStatusLogger StatusLogger = new ProgressDialogStatusLogger(); //default: empty but not null
private Activity _activeActivity;
protected RunnableOnFinish(Activity activeActivity, OnFinish finish)
{
_activeActivity = activeActivity;
_onFinishToRun = finish;
}
public OnFinish OnFinishToRun
{
get { return _onFinishToRun; }
set { _onFinishToRun = value; }
}
public Activity ActiveActivity
{
get { return _activeActivity; }
set
{
_activeActivity = value;
if (_onFinishToRun != null)
_onFinishToRun.ActiveActivity = _activeActivity;
}
}
protected void Finish(bool result, String message, bool importantMessage = false, Exception exception = null) {
if ( OnFinishToRun != null ) {
OnFinishToRun.SetResult(result, message, importantMessage, exception);
OnFinishToRun.Run();
}
}
protected void Finish(bool result) {
if ( OnFinishToRun != null ) {
OnFinishToRun.SetResult(result);
OnFinishToRun.Run();
}
}
public void SetStatusLogger(ProgressDialogStatusLogger status) {
if (OnFinishToRun != null)
{
OnFinishToRun.StatusLogger = status;
}
StatusLogger = status;
}
public abstract void Run();
}
}

View File

@@ -30,69 +30,57 @@ using keepass2android.Io;
using Debug = System.Diagnostics.Debug; using Debug = System.Diagnostics.Debug;
using Exception = System.Exception; using Exception = System.Exception;
using KeePass.Util; using KeePass.Util;
using Thread = System.Threading.Thread;
namespace keepass2android namespace keepass2android
{ {
/// <summary> public class SaveDb : RunnableOnFinish {
/// Save the database. If the file has changed, ask the user if he wants to overwrite or sync.
/// </summary>
public class SaveDb : OperationWithFinishHandler {
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly Database _db; private readonly Database _db;
private readonly bool _dontSave; private readonly bool _dontSave;
private readonly IDatabaseModificationWatcher _modificationWatcher;
private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving.
public bool DoNotSetStatusLoggerMessage = false;
/// <summary> /// <summary>
/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync /// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync
/// </summary> /// </summary>
private readonly Stream _streamForOrigFile; private readonly Stream _streamForOrigFile;
private readonly Context _ctx;
private Java.Lang.Thread _workerThread; private Java.Lang.Thread _workerThread;
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler, bool dontSave, IDatabaseModificationWatcher modificationWatcher) public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish, bool dontSave)
: base(app, operationFinishedHandler) : base(ctx, finish)
{ {
_db = db; _db = db;
_ctx = ctx;
_app = app; _app = app;
_dontSave = dontSave; _dontSave = dontSave;
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
} }
/// <summary> /// <summary>
/// Constructor for sync /// Constructor for sync
/// </summary> /// </summary>
/// <param name="ctx"></param>
/// <param name="app"></param> /// <param name="app"></param>
/// <param name="operationFinishedHandler"></param> /// <param name="finish"></param>
/// <param name="dontSave"></param> /// <param name="dontSave"></param>
/// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param> /// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param>
public SaveDb(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, Database db, bool dontSave, Stream streamForOrigFile, IDatabaseModificationWatcher modificationWatcher = null) public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish, Database db, bool dontSave, Stream streamForOrigFile)
: base(app, operationFinishedHandler) : base(ctx, finish)
{ {
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher(); _db = db;
_db = db; _ctx = ctx;
_app = app; _app = app;
_dontSave = dontSave; _dontSave = dontSave;
_streamForOrigFile = streamForOrigFile; _streamForOrigFile = streamForOrigFile;
SyncInBackground = _app.SyncInBackgroundPreference; }
} public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish)
: base(ctx, finish)
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher = null)
: base(app, operationFinishedHandler)
{ {
_ctx = ctx;
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher(); _app = app;
_app = app;
_db = db; _db = db;
_dontSave = false; _dontSave = false;
SyncInBackground = _app.SyncInBackgroundPreference; }
}
public bool ShowDatabaseIocInStatus { get; set; } public bool ShowDatabaseIocInStatus { get; set; }
@@ -115,42 +103,29 @@ namespace keepass2android
if (ShowDatabaseIocInStatus) if (ShowDatabaseIocInStatus)
message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")"; message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
if (!DoNotSetStatusLoggerMessage) StatusLogger.UpdateMessage(message);
{
StatusLogger.UpdateMessage(message);
}
IOConnectionInfo ioc = _db.Ioc; IOConnectionInfo ioc = _db.Ioc;
IFileStorage fileStorage = _app.GetFileStorage(ioc); IFileStorage fileStorage = _app.GetFileStorage(ioc);
if (SyncInBackground && fileStorage is IOfflineSwitchable offlineSwitchable) if (_streamForOrigFile == null)
{
offlineSwitchable.IsOffline = true;
//no warning. We'll trigger a sync later.
offlineSwitchable.TriggerWarningWhenFallingBackToCache = false;
requiresSubsequentSync = true;
}
if (_streamForOrigFile == null)
{ {
if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave))
|| (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving || (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving
{ {
PerformSaveWithoutCheck(fileStorage, ioc); PerformSaveWithoutCheck(fileStorage, ioc);
FinishWithSuccess(); Finish(true);
return; return;
} }
} }
bool hasStreamForOrigFile = (_streamForOrigFile != null); bool hasStreamForOrigFile = (_streamForOrigFile != null);
bool hasChangeFast = hasStreamForOrigFile || bool hasChangeFast = hasStreamForOrigFile ||
fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection; fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection;
bool hasHashChanged = !requiresSubsequentSync && ( bool hasHashChanged = hasChangeFast ||
hasChangeFast ||
(FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) == (FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) ==
FileHashChange.Changed)); //if that fails, hash the file and compare: FileHashChange.Changed); //if that fails, hash the file and compare:
if (hasHashChanged) if (hasHashChanged)
{ {
@@ -183,14 +158,15 @@ namespace keepass2android
RunInWorkerThread(() => RunInWorkerThread(() =>
{ {
PerformSaveWithoutCheck(fileStorage, ioc); PerformSaveWithoutCheck(fileStorage, ioc);
FinishWithSuccess(); Finish(true);
}); });
}, },
//cancel //cancel
(sender, args) => (sender, args) =>
{ {
RunInWorkerThread(() => Finish(false)); RunInWorkerThread(() => Finish(false));
} },
_ctx
); );
} }
@@ -198,7 +174,7 @@ namespace keepass2android
else else
{ {
PerformSaveWithoutCheck(fileStorage, ioc); PerformSaveWithoutCheck(fileStorage, ioc);
FinishWithSuccess(); Finish(true);
} }
} }
@@ -218,67 +194,22 @@ namespace keepass2android
} }
else else
{ {
FinishWithSuccess(); Finish(true);
} }
} }
public bool SyncInBackground { get; set; }
private void FinishWithSuccess()
{
if (requiresSubsequentSync)
{
var syncTask = new SynchronizeCachedDatabase(_app, _db, new ActionOnOperationFinished(_app,
(success, message, context) =>
{
if (!System.String.IsNullOrEmpty(message))
_app.ShowMessage(context, message, success ? MessageSeverity.Info : MessageSeverity.Error);
}), new BackgroundDatabaseModificationLocker(_app)
);
OperationRunner.Instance.Run(_app, syncTask);
}
Finish(true);
}
private void MergeAndFinish(IFileStorage fileStorage, IOConnectionInfo ioc) private void MergeAndFinish(IFileStorage fileStorage, IOConnectionInfo ioc)
{ {
//note: when synced, the file might be downloaded once again from the server. Caching the data //note: when synced, the file might be downloaded once again from the server. Caching the data
//in the hashing function would solve this but increases complexity. I currently assume the files are //in the hashing function would solve this but increases complexity. I currently assume the files are
//small. //small.
MergeIn(fileStorage, ioc);
try
{
_modificationWatcher.BeforeModifyDatabases();
}
catch (Java.Lang.InterruptedException)
{
// leave without Finish()
return;
}
try
{
MergeIn(fileStorage, ioc);
}
finally
{
_modificationWatcher.AfterModifyDatabases();
}
PerformSaveWithoutCheck(fileStorage, ioc); PerformSaveWithoutCheck(fileStorage, ioc);
new Handler(Looper.MainLooper).Post(() => _db.UpdateGlobals();
{ Finish(true);
_db.UpdateGlobals();
});
FinishWithSuccess();
} }
private void RunInWorkerThread(Action runHandler) private void RunInWorkerThread(Action runHandler)
{ {
try try
@@ -351,7 +282,7 @@ namespace keepass2android
private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc) private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc)
{ {
StatusLogger.UpdateSubMessage(""); StatusLogger.UpdateSubMessage("");
_db.SaveData(fileStorage); _db.SaveData();
_db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc); _db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
} }

View File

@@ -22,24 +22,26 @@ using KeePassLib.Keys;
namespace keepass2android namespace keepass2android
{ {
public class SetPassword : OperationWithFinishHandler { public class SetPassword : RunnableOnFinish {
private readonly String _password; private readonly String _password;
private readonly String _keyfile; private readonly String _keyfile;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly bool _dontSave; private readonly bool _dontSave;
private readonly Activity _ctx;
public SetPassword(IKp2aApp app, String password, String keyfile, OnOperationFinishedHandler operationFinishedHandler): base(app, operationFinishedHandler) { public SetPassword(Activity ctx, IKp2aApp app, String password, String keyfile, OnFinish finish): base(ctx, finish) {
_ctx = ctx;
_app = app; _app = app;
_password = password; _password = password;
_keyfile = keyfile; _keyfile = keyfile;
_dontSave = false; _dontSave = false;
} }
public SetPassword(IKp2aApp app, String password, String keyfile, OnOperationFinishedHandler operationFinishedHandler, bool dontSave) public SetPassword(Activity ctx, IKp2aApp app, String password, String keyfile, OnFinish finish, bool dontSave)
: base(app, operationFinishedHandler) : base(ctx, finish)
{ {
_ctx = ctx;
_app = app; _app = app;
_password = password; _password = password;
_keyfile = keyfile; _keyfile = keyfile;
@@ -71,18 +73,18 @@ namespace keepass2android
pm.MasterKey = newKey; pm.MasterKey = newKey;
// Save Database // Save Database
_operationFinishedHandler = new AfterSave(_app, previousKey, previousMasterKeyChanged, pm, operationFinishedHandler); _onFinishToRun = new AfterSave(ActiveActivity, previousKey, previousMasterKeyChanged, pm, OnFinishToRun);
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler, _dontSave, null); SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun, _dontSave);
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
private class AfterSave : OnOperationFinishedHandler { private class AfterSave : OnFinish {
private readonly CompositeKey _backup; private readonly CompositeKey _backup;
private readonly DateTime _previousKeyChanged; private readonly DateTime _previousKeyChanged;
private readonly PwDatabase _db; private readonly PwDatabase _db;
public AfterSave(IActiveContextProvider activeContextProvider, CompositeKey backup, DateTime previousKeyChanged, PwDatabase db, OnOperationFinishedHandler operationFinishedHandler): base(activeContextProvider, operationFinishedHandler) { public AfterSave(Activity activity, CompositeKey backup, DateTime previousKeyChanged, PwDatabase db, OnFinish finish): base(activity, finish) {
_previousKeyChanged = previousKeyChanged; _previousKeyChanged = previousKeyChanged;
_backup = backup; _backup = backup;
_db = db; _db = db;

View File

@@ -22,29 +22,31 @@ using KeePassLib;
namespace keepass2android namespace keepass2android
{ {
public class UpdateEntry : OperationWithFinishHandler { public class UpdateEntry : RunnableOnFinish {
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly Activity _ctx;
public UpdateEntry(IKp2aApp app, PwEntry oldE, PwEntry newE, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) { public UpdateEntry(Activity ctx, IKp2aApp app, PwEntry oldE, PwEntry newE, OnFinish finish):base(ctx, finish) {
_app = app; _ctx = ctx;
_app = app;
_operationFinishedHandler = new AfterUpdate( oldE, newE, app, operationFinishedHandler); _onFinishToRun = new AfterUpdate(ctx, oldE, newE, app, finish);
} }
public override void Run() { public override void Run() {
// Commit to disk // Commit to disk
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler); SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun);
save.SetStatusLogger(StatusLogger); save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
private class AfterUpdate : OnOperationFinishedHandler { private class AfterUpdate : OnFinish {
private readonly PwEntry _backup; private readonly PwEntry _backup;
private readonly PwEntry _updatedEntry; private readonly PwEntry _updatedEntry;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
public AfterUpdate(PwEntry backup, PwEntry updatedEntry, IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) { public AfterUpdate(Activity activity, PwEntry backup, PwEntry updatedEntry, IKp2aApp app, OnFinish finish):base(activity, finish) {
_backup = backup; _backup = backup;
_updatedEntry = updatedEntry; _updatedEntry = updatedEntry;
_app = app; _app = app;

View File

@@ -20,7 +20,6 @@ git clone --recurse-submodules https://github.com/PhilippC/keepass2android.git
cd keepass2android/src/build-scripts cd keepass2android/src/build-scripts
./build-java.sh && ./build-native.sh ./build-java.sh && ./build-native.sh
cd .. cd ..
cp Kp2aBusinessLogic/Io/DropboxFileStorageKeysDummy.cs Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs
cd keepass2android-app cd keepass2android-app
ln -s Manifests/AndroidManifest_debug.xml AndroidManifest.xml ln -s Manifests/AndroidManifest_debug.xml AndroidManifest.xml
dotnet workload restore dotnet workload restore

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
BASE_DIR="${1}"
for arch_dir in "$BASE_DIR"/android-*/; do
arch=$(basename "$arch_dir")
arch=${arch#android-}
APK_DIR="${arch_dir}publish"
if [[ -d "$APK_DIR" ]]; then
apk_path=$(find "$APK_DIR" -maxdepth 1 -type f -name "*.apk" | head -n1)
if [[ -n "$apk_path" ]]; then
base=$(basename "$apk_path" .apk)
new_path="$APK_DIR/${base}-${arch}.apk"
mv "$apk_path" "$new_path"
echo "Renamed $apk_path to $new_path"
else
echo "No APK found in $APK_DIR"
fi
else
echo "Directory $APK_DIR does not exist"
fi
done

View File

@@ -1,6 +1,7 @@
package keepass2android.javafilestorage; package keepass2android.javafilestorage;
import android.content.Context; import android.content.Context;
import java.math.BigInteger;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@@ -15,7 +16,10 @@ import com.burgstaller.okhttp.basic.BasicAuthenticator;
import com.burgstaller.okhttp.digest.CachingAuthenticator; import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.DigestAuthenticator; import com.burgstaller.okhttp.digest.DigestAuthenticator;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringReader; import java.io.StringReader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -24,6 +28,7 @@ import java.security.KeyManagementException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
@@ -44,23 +49,33 @@ import keepass2android.javafilestorage.webdav.DecoratedTrustManager;
import keepass2android.javafilestorage.webdav.PropfindXmlParser; import keepass2android.javafilestorage.webdav.PropfindXmlParser;
import keepass2android.javafilestorage.webdav.WebDavUtil; import keepass2android.javafilestorage.webdav.WebDavUtil;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.internal.tls.OkHostnameVerifier; import okhttp3.internal.tls.OkHostnameVerifier;
import okio.BufferedSink;
public class WebDavStorage extends JavaFileStorageBase { public class WebDavStorage extends JavaFileStorageBase {
private final ICertificateErrorHandler mCertificateErrorHandler; private final ICertificateErrorHandler mCertificateErrorHandler;
private Context appContext; private Context appContext;
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler) int chunkSize;
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler, int chunkSize)
{ {
this.chunkSize = chunkSize;
mCertificateErrorHandler = certificateErrorHandler; mCertificateErrorHandler = certificateErrorHandler;
} }
public void setUploadChunkSize(int chunkSize)
{
this.chunkSize = chunkSize;
}
public String buildFullPath(String url, String username, String password) throws UnsupportedEncodingException { public String buildFullPath(String url, String username, String password) throws UnsupportedEncodingException {
String scheme = url.substring(0, url.indexOf("://")); String scheme = url.substring(0, url.indexOf("://"));
url = url.substring(scheme.length() + 3); url = url.substring(scheme.length() + 3);
@@ -181,21 +196,119 @@ public class WebDavStorage extends JavaFileStorageBase {
return client; return client;
} }
public void renameOrMoveWebDavResource(String sourcePath, String destinationPath, boolean overwrite) throws Exception {
ConnectionInfo sourceCi = splitStringToConnectionInfo(sourcePath);
ConnectionInfo destinationCi = splitStringToConnectionInfo(destinationPath);
Request.Builder requestBuilder = new Request.Builder()
.url(new URL(sourceCi.URL))
.method("MOVE", null) // "MOVE" is the HTTP method
.header("Destination", destinationCi.URL); // New URI for the resource
// Add Overwrite header
if (overwrite) {
requestBuilder.header("Overwrite", "T"); // 'T' for true
} else {
requestBuilder.header("Overwrite", "F"); // 'F' for false
}
Request request = requestBuilder.build();
Response response = getClient(sourceCi).newCall(request).execute();
// Check the status code
if (response.isSuccessful()) {
// WebDAV MOVE can return 201 (Created) if a new resource was created at dest,
// or 204 (No Content) if moved to a pre-existing destination (e.g., just renamed).
// A 200 OK might also be returned by some servers, though 201/204 are more common.
}
else
{
throw new Exception("Rename/Move failed for " + sourceCi.URL + " to " + destinationCi.URL + ": " + response.code() + " " + response.message());
}
}
public static String generateRandomHexString(int length) {
SecureRandom secureRandom = new SecureRandom();
// Generate enough bytes to ensure we can get the desired number of hex characters.
// Each byte converts to two hex characters.
// For 8 hex characters, we need 4 bytes.
int numBytes = (int) Math.ceil(length / 2.0);
byte[] randomBytes = new byte[numBytes];
secureRandom.nextBytes(randomBytes);
// Convert the byte array to a hexadecimal string
// BigInteger(1, randomBytes) treats the byte array as a positive number.
// toString(16) converts it to a hexadecimal string.
String hexString = new BigInteger(1, randomBytes).toString(16);
// Pad with leading zeros if necessary (e.g., if the generated number is small)
// and then take the first 'length' characters.
// Using String.format to ensure leading zeros if the hexString is shorter.
return String.format("%0" + length + "d", new BigInteger(hexString, 16)).substring(0, length);
}
@Override @Override
public void uploadFile(String path, byte[] data, boolean writeTransactional) public void uploadFile(String path, byte[] data, boolean writeTransactional)
throws Exception { throws Exception {
if (writeTransactional)
{
String randomSuffix = ".tmp." + generateRandomHexString(8);
uploadFile(path + randomSuffix, data, false);
renameOrMoveWebDavResource(path+randomSuffix, path, true);
return;
}
try { try {
ConnectionInfo ci = splitStringToConnectionInfo(path); ConnectionInfo ci = splitStringToConnectionInfo(path);
RequestBody requestBody;
if (chunkSize > 0)
{
// use chunked upload
requestBody = new RequestBody() {
@Override
public MediaType contentType() {
return MediaType.parse("application/binary");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
try (InputStream in = new ByteArrayInputStream(data)) {
byte[] buffer = new byte[chunkSize];
int read;
while ((read = in.read(buffer)) != -1) {
sink.write(buffer, 0, read);
sink.flush();
}
}
}
@Override
public long contentLength() {
return -1; // use chunked upload
}
};
}
else
{
requestBody = new MultipartBody.Builder()
.addPart(RequestBody.create(data, MediaType.parse("application/binary")))
.build();
}
Request request = new Request.Builder() Request request = new Request.Builder()
.url(new URL(ci.URL)) .url(new URL(ci.URL))
.put(RequestBody.create(MediaType.parse("application/binary"), data)) .put(requestBody)
.build(); .build();
//TODO consider writeTransactional
//TODO check for error
Response response = getClient(ci).newCall(request).execute(); Response response = getClient(ci).newCall(request).execute();
checkStatus(response); checkStatus(response);
@@ -290,7 +403,10 @@ public class WebDavStorage extends JavaFileStorageBase {
e.sizeInBytes = -1; e.sizeInBytes = -1;
} }
} }
e.isDirectory = r.href.endsWith("/");
e.isDirectory = r.href.endsWith("/") || okprop.IsCollection;
e.displayName = okprop.DisplayName; e.displayName = okprop.DisplayName;
if (e.displayName == null) if (e.displayName == null)
@@ -519,3 +635,4 @@ public class WebDavStorage extends JavaFileStorageBase {
} }
} }

View File

@@ -57,6 +57,8 @@ public class PropfindXmlParser
public String DisplayName; public String DisplayName;
public String LastModified; public String LastModified;
public String ContentLength; public String ContentLength;
public boolean IsCollection;
} }
public String status; public String status;
public Prop prop; public Prop prop;
@@ -191,6 +193,8 @@ public class PropfindXmlParser
continue; continue;
} }
String name = parser.getName(); String name = parser.getName();
String namespace = parser.getNamespace();
android.util.Log.d("PARSE", "4name = " + name); android.util.Log.d("PARSE", "4name = " + name);
if (name.equals("getcontentlength")) if (name.equals("getcontentlength"))
@@ -200,6 +204,9 @@ public class PropfindXmlParser
prop.LastModified = readText(parser); prop.LastModified = readText(parser);
} else if (name.equals("displayname")) { } else if (name.equals("displayname")) {
prop.DisplayName = readText(parser); prop.DisplayName = readText(parser);
} else if (name.equals("resourcetype") && namespace.equals(ns)) {
// We found the <d:resourcetype> tag
prop.IsCollection = readResourceType(parser);
} else { } else {
skip(parser); skip(parser);
} }
@@ -208,6 +215,37 @@ public class PropfindXmlParser
return prop; return prop;
} }
private boolean readResourceType(XmlPullParser parser) throws IOException, XmlPullParserException {
boolean isCollection = false;
parser.require(XmlPullParser.START_TAG, ns, "resourcetype");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
String namespace = parser.getNamespace();
if (name.equals("collection") && namespace.equals(ns)) {
// We found <d:collection/>, so it's a folder
isCollection = true;
// Since <d:collection/> is usually an empty tag, just consume it.
// It might contain text if there's whitespace, so consume text then end tag.
if (parser.next() == XmlPullParser.TEXT) {
parser.nextTag(); // Move to the end tag
}
parser.require(XmlPullParser.END_TAG, ns, "collection");
} else {
// Skip any other unexpected tags within <d:resourcetype>
skip(parser);
}
}
// After reading all children of <d:resourcetype>, ensure we are at its END_TAG
parser.require(XmlPullParser.END_TAG, ns, "resourcetype");
return isCollection;
}
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
android.util.Log.d("PARSE", "skipping " + parser.getName()); android.util.Log.d("PARSE", "skipping " + parser.getName());

View File

@@ -6,8 +6,7 @@
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true">
android:theme="@style/AppTheme">
<activity android:name="com.crocoapps.javafilestoragetest2.MainActivity" <activity android:name="com.crocoapps.javafilestoragetest2.MainActivity"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>

View File

@@ -548,7 +548,7 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
//storageToTest = new GoogleDriveAppDataFileStorage(); //storageToTest = new GoogleDriveAppDataFileStorage();
/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() { storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
@Override @Override
public boolean onValidationError(String error) { public boolean onValidationError(String error) {
return false; return false;
@@ -558,12 +558,12 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
public boolean alwaysFailOnValidationError() { public boolean alwaysFailOnValidationError() {
return false; return false;
} }
}); }, 64*1024);
*/
//storageToTest = new DropboxV2Storage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart); //storageToTest = new DropboxV2Storage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
//storageToTest = new DropboxFileStorage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart); //storageToTest = new DropboxFileStorage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
//storageToTest = new DropboxAppFolderFileStorage(ctx,"ax0268uydp1ya57", "3s86datjhkihwyc", true); //storageToTest = new DropboxAppFolderFileStorage(ctx,"ax0268uydp1ya57", "3s86datjhkihwyc", true);
storageToTest = new GoogleDriveFullFileStorage(); // storageToTest = new GoogleDriveFullFileStorage();
return storageToTest; return storageToTest;

View File

@@ -56,15 +56,15 @@
<string name="afc_title_sort_by">Ordina per…</string> <string name="afc_title_sort_by">Ordina per…</string>
<string name="afc_yesterday">Ieri</string> <string name="afc_yesterday">Ieri</string>
<plurals name="afc_title_choose_directories"> <plurals name="afc_title_choose_directories">
<item quantity="one">Scegli la cartella&#8230;</item> <item quantity="one">Scegli cartella...</item>
<item quantity="other">Scegli le cartelle&#8230;</item> <item quantity="other">Scegli le cartelle...</item>
</plurals> </plurals>
<plurals name="afc_title_choose_files"> <plurals name="afc_title_choose_files">
<item quantity="one">Scegli il file&#8230;</item> <item quantity="one">Scegli il file</item>
<item quantity="other">Scegli i file&#8230;</item> <item quantity="other">Scegli i file</item>
</plurals> </plurals>
<plurals name="afc_title_choose_files_directories"> <plurals name="afc_title_choose_files_directories">
<item quantity="one">Scegli file/ cartella&#8230;</item> <item quantity="one">Scegli file/cartella</item>
<item quantity="other">Scegli file/ cartelle&#8230;</item> <item quantity="other">Scegli file/cartelle</item>
</plurals> </plurals>
</resources> </resources>

View File

@@ -14,7 +14,7 @@ using keepass2android;
namespace keepass2android namespace keepass2android
{ {
[Activity(Label = AppNames.AppName)] [Activity(Label = AppNames.AppName, Theme = "@style/Kp2aTheme_BlueNoActionBar")]
public class AppKilledInfo : Activity, IDialogInterfaceOnDismissListener public class AppKilledInfo : Activity, IDialogInterfaceOnDismissListener
{ {
protected override void OnCreate(Bundle bundle) protected override void OnCreate(Bundle bundle)

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,8 @@ namespace keepass2android
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title)); builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title));
List<string> changeLog = new List<string>{ List<string> changeLog = new List<string>{
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_13}, "1.13"),
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_12 BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_12
#if !NoNet #if !NoNet
,Resource.Array.ChangeLog_1_12_net ,Resource.Array.ChangeLog_1_12_net

View File

@@ -228,9 +228,9 @@ namespace keepass2android
newEntry.SetUuid(new PwUuid(true), true); // Create new UUID newEntry.SetUuid(new PwUuid(true), true); // Create new UUID
string strTitle = newEntry.Strings.ReadSafe(PwDefs.TitleField); string strTitle = newEntry.Strings.ReadSafe(PwDefs.TitleField);
newEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, strTitle + " (" + Android.OS.Build.Model + ")")); newEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, strTitle + " (" + Android.OS.Build.Model + ")"));
var addTask = new AddEntry( App.Kp2a.CurrentDb, App.Kp2a, newEntry,item.Entry.ParentGroup,new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => (context as ConfigureChildDatabasesActivity)?.Update())); var addTask = new AddEntry(this, App.Kp2a.CurrentDb, App.Kp2a, newEntry,item.Entry.ParentGroup,new ActionOnFinish(this, (success, message, activity) => ((ConfigureChildDatabasesActivity)activity).Update()));
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, addTask); ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
pt.Run(); pt.Run();
} }
@@ -260,9 +260,9 @@ namespace keepass2android
private void Save(AutoExecItem item) private void Save(AutoExecItem item)
{ {
var addTask = new SaveDb(App.Kp2a, App.Kp2a.FindDatabaseForElement(item.Entry), new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => (context as ConfigureChildDatabasesActivity)?.Update())); var addTask = new SaveDb(this, App.Kp2a, App.Kp2a.FindDatabaseForElement(item.Entry), new ActionOnFinish(this, (success, message, activity) => ((ConfigureChildDatabasesActivity)activity).Update()));
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, addTask); ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
pt.Run(); pt.Run();
} }
@@ -343,7 +343,7 @@ namespace keepass2android
} }
if (autoOpenGroup == null) if (autoOpenGroup == null)
{ {
AddGroup addGroupTask = AddGroup.GetInstance(App.Kp2a, "AutoOpen", 1, null, rootGroup, null, true); AddGroup addGroupTask = AddGroup.GetInstance(this, App.Kp2a, "AutoOpen", 1, null, rootGroup, null, true);
addGroupTask.Run(); addGroupTask.Run();
autoOpenGroup = addGroupTask.Group; autoOpenGroup = addGroupTask.Group;
} }
@@ -367,9 +367,9 @@ namespace keepass2android
{KeeAutoExecExt.ThisDeviceId, true} {KeeAutoExecExt.ThisDeviceId, true}
}))); })));
var addTask = new AddEntry( db, App.Kp2a, newEntry, autoOpenGroup, new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => (context as ConfigureChildDatabasesActivity)?.Update())); var addTask = new AddEntry(this, db, App.Kp2a, newEntry, autoOpenGroup, new ActionOnFinish(this, (success, message, activity) => (activity as ConfigureChildDatabasesActivity)?.Update()));
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, addTask); ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
pt.Run(); pt.Run();
} }

View File

@@ -213,9 +213,10 @@ namespace keepass2android
} }
// Create the new database // Create the new database
CreateDb create = new CreateDb(App.Kp2a, this, _ioc, new LaunchGroupActivity(_ioc, App.Kp2a, this), false, newKey, makeCurrent); CreateDb create = new CreateDb(App.Kp2a, this, _ioc, new LaunchGroupActivity(_ioc, this), false, newKey, makeCurrent);
BlockingOperationStarter createTask = new BlockingOperationStarter( ProgressTask createTask = new ProgressTask(
App.Kp2a, create); App.Kp2a,
this, create);
createTask.Run(); createTask.Run();
} }
@@ -316,7 +317,7 @@ namespace keepass2android
if (resultCode == KeePass.ResultOkPasswordGenerator) if (resultCode == KeePass.ResultOkPasswordGenerator)
{ {
String generatedPassword = data.GetStringExtra(GeneratePasswordActivity.GeneratedPasswordKey); String generatedPassword = data.GetStringExtra("keepass2android.password.generated_password");
FindViewById<TextView>(Resource.Id.entry_password).Text = generatedPassword; FindViewById<TextView>(Resource.Id.entry_password).Text = generatedPassword;
FindViewById<TextView>(Resource.Id.entry_confpassword).Text = generatedPassword; FindViewById<TextView>(Resource.Id.entry_confpassword).Text = generatedPassword;
} }
@@ -402,14 +403,14 @@ namespace keepass2android
private class LaunchGroupActivity : FileOnFinish private class LaunchGroupActivity : FileOnFinish
{ {
readonly CreateDatabaseActivity _activity;
private readonly IOConnectionInfo _ioc; private readonly IOConnectionInfo _ioc;
private readonly CreateDatabaseActivity _activity;
public LaunchGroupActivity(IOConnectionInfo ioc, IKp2aApp app, CreateDatabaseActivity activity) public LaunchGroupActivity(IOConnectionInfo ioc, CreateDatabaseActivity activity)
: base(app, null) : base(activity, null)
{ {
_activity = activity; _activity = activity;
_ioc = ioc; _ioc = ioc;
} }
public override void Run() public override void Run()
@@ -419,7 +420,7 @@ namespace keepass2android
// Update the ongoing notification // Update the ongoing notification
App.Kp2a.UpdateOngoingNotification(); App.Kp2a.UpdateOngoingNotification();
if (PreferenceManager.GetDefaultSharedPreferences(App.Context).GetBoolean(App.Context.GetString(Resource.String.RememberRecentFiles_key), App.Context.Resources.GetBoolean(Resource.Boolean.RememberRecentFiles_default))) if (PreferenceManager.GetDefaultSharedPreferences(_activity).GetBoolean(_activity.GetString(Resource.String.RememberRecentFiles_key), _activity.Resources.GetBoolean(Resource.Boolean.RememberRecentFiles_default)))
{ {
// Add to recent files // Add to recent files
FileDbHelper dbHelper = App.Kp2a.FileDbHelper; FileDbHelper dbHelper = App.Kp2a.FileDbHelper;

View File

@@ -4,12 +4,12 @@ using KeePassLib.Serialization;
namespace keepass2android namespace keepass2android
{ {
class CreateNewFilename : OperationWithFinishHandler class CreateNewFilename : RunnableOnFinish
{ {
private readonly string _filename; private readonly string _filename;
public CreateNewFilename(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, string filename) public CreateNewFilename(Activity activity, OnFinish finish, string filename)
: base(app,operationFinishedHandler) : base(activity,finish)
{ {
_filename = filename; _filename = filename;
} }

View File

@@ -19,7 +19,7 @@ using keepass2android.services.AutofillBase;
namespace keepass2android namespace keepass2android
{ {
[Activity(Label = "DisableAutofillForQueryActivity")] [Activity(Label = "DisableAutofillForQueryActivity", Theme = "@style/Kp2aTheme_ActionBar")]
public class DisableAutofillForQueryActivity : Activity public class DisableAutofillForQueryActivity : Activity
{ {
public IAutofillIntentBuilder IntentBuilder = new Kp2aAutofillIntentBuilder(); public IAutofillIntentBuilder IntentBuilder = new Kp2aAutofillIntentBuilder();
@@ -63,8 +63,6 @@ namespace keepass2android
prefs.Edit().PutStringSet("AutoFillDisabledQueries", disabledValues).Commit(); prefs.Edit().PutStringSet("AutoFillDisabledQueries", disabledValues).Commit();
bool isManual = Intent.GetBooleanExtra(ChooseForAutofillActivityBase.ExtraIsManualRequest, false);
Intent reply = new Intent(); Intent reply = new Intent();
FillResponse.Builder builder = new FillResponse.Builder(); FillResponse.Builder builder = new FillResponse.Builder();
AssistStructure structure = (AssistStructure)Intent.GetParcelableExtra(AutofillManager.ExtraAssistStructure); AssistStructure structure = (AssistStructure)Intent.GetParcelableExtra(AutofillManager.ExtraAssistStructure);
@@ -77,7 +75,7 @@ namespace keepass2android
StructureParser parser = new StructureParser(this, structure); StructureParser parser = new StructureParser(this, structure);
try try
{ {
parser.ParseForFill(isManual); parser.ParseForFill();
} }
catch (Java.Lang.SecurityException e) catch (Java.Lang.SecurityException e)

View File

@@ -56,7 +56,6 @@ using Android.Util;
using AndroidX.Core.Content; using AndroidX.Core.Content;
using Google.Android.Material.Dialog; using Google.Android.Material.Dialog;
using keepass2android; using keepass2android;
using keepass2android.views;
namespace keepass2android namespace keepass2android
{ {
@@ -64,7 +63,7 @@ namespace keepass2android
{ {
private readonly string _binaryToSave; private readonly string _binaryToSave;
public ExportBinaryProcessManager(int requestCode, LifecycleAwareActivity activity, string key) : base(requestCode, activity) public ExportBinaryProcessManager(int requestCode, Activity activity, string key) : base(requestCode, activity)
{ {
_binaryToSave = key; _binaryToSave = key;
} }
@@ -76,13 +75,13 @@ namespace keepass2android
protected override void SaveFile(IOConnectionInfo ioc) protected override void SaveFile(IOConnectionInfo ioc)
{ {
var task = new EntryActivity.WriteBinaryTask(App.Kp2a, new ActionOnOperationFinished(App.Kp2a, (success, message, context) => var task = new EntryActivity.WriteBinaryTask(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) =>
{ {
if (!success) if (!success)
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error); App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
} }
), ((EntryActivity)_activity).Entry.Binaries.Get(_binaryToSave), ioc); ), ((EntryActivity)_activity).Entry.Binaries.Get(_binaryToSave), ioc);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, task); ProgressTask pt = new ProgressTask(App.Kp2a, _activity, task);
pt.Run(); pt.Run();
} }
@@ -90,7 +89,6 @@ namespace keepass2android
public override void OnSaveInstanceState(Bundle outState) public override void OnSaveInstanceState(Bundle outState)
{ {
outState.PutString("BinaryToSave", _binaryToSave); outState.PutString("BinaryToSave", _binaryToSave);
base.OnSaveInstanceState(outState);
} }
@@ -99,7 +97,7 @@ namespace keepass2android
[Activity (Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden, [Activity (Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden,
Theme = "@style/Kp2aTheme_ActionBar")] Theme = "@style/Kp2aTheme_ActionBar")]
public class EntryActivity : LockCloseActivity, IProgressUiProvider public class EntryActivity : LockCloseActivity
{ {
public const String KeyEntry = "entry"; public const String KeyEntry = "entry";
public const String KeyRefreshPos = "refresh_pos"; public const String KeyRefreshPos = "refresh_pos";
@@ -112,45 +110,6 @@ namespace keepass2android
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
public class UpdateEntryActivityBroadcastReceiver : BroadcastReceiver
{
private readonly EntryActivity _activity;
public UpdateEntryActivityBroadcastReceiver(EntryActivity activity)
{
_activity = activity;
}
public override void OnReceive(Context? context, Intent? intent)
{
if (intent?.Action == Intents.DataUpdated)
{
_activity.OnDataUpdated();
}
}
}
private void OnDataUpdated()
{
if (Entry == null)
{
return;
}
var entryUId = Entry.Uuid;
if (!App.Kp2a.CurrentDb.EntriesById.ContainsKey(entryUId))
{
Finish();
return;
}
var newEntry = App.Kp2a.CurrentDb.EntriesById[entryUId];
if (!newEntry.EqualsEntry(Entry, PwCompareOptions.None, MemProtCmpMode.Full))
{
Recreate();
}
}
public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1) public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1)
{ {
Intent i = new Intent(act, typeof(EntryActivity)); Intent i = new Intent(act, typeof(EntryActivity));
@@ -522,8 +481,8 @@ namespace keepass2android
Entry.Expires = true; Entry.Expires = true;
Entry.Touch(true); Entry.Touch(true);
RequiresRefresh(); RequiresRefresh();
UpdateEntry update = new UpdateEntry(App.Kp2a, backupEntry, Entry, null); UpdateEntry update = new UpdateEntry(this, App.Kp2a, backupEntry, Entry, null);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, update); ProgressTask pt = new ProgressTask(App.Kp2a, this, update);
pt.Run(); pt.Run();
} }
FillData(); FillData();
@@ -542,13 +501,7 @@ namespace keepass2android
//the rest of the things to do depends on the current app task: //the rest of the things to do depends on the current app task:
AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread); AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread);
}
_dataUpdatedIntentReceiver = new UpdateEntryActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter();
filter.AddAction(Intents.DataUpdated);
ContextCompat.RegisterReceiver(this, _dataUpdatedIntentReceiver, filter, (int)ReceiverFlags.Exported);
}
private void RemoveFromHistory() private void RemoveFromHistory()
{ {
@@ -572,17 +525,13 @@ namespace keepass2android
App.Kp2a.DirtyGroups.Add(parent); App.Kp2a.DirtyGroups.Add(parent);
} }
var saveTask = new SaveDb( App.Kp2a, App.Kp2a.FindDatabaseForElement(Entry), new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => var saveTask = new SaveDb(this, App.Kp2a, App.Kp2a.FindDatabaseForElement(Entry), new ActionOnFinish(this, (success, message, activity) =>
{ {
if (context is Activity activity) activity.SetResult(KeePass.ExitRefresh);
{ activity.Finish();
activity.SetResult(KeePass.ExitRefresh);
activity.Finish();
}
})); }));
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, saveTask); ProgressTask pt = new ProgressTask(App.Kp2a, this, saveTask);
pt.Run(); pt.Run();
} }
@@ -1129,9 +1078,7 @@ namespace keepass2android
UnregisterReceiver(_pluginActionReceiver); UnregisterReceiver(_pluginActionReceiver);
if (_pluginFieldReceiver != null) if (_pluginFieldReceiver != null)
UnregisterReceiver(_pluginFieldReceiver); UnregisterReceiver(_pluginFieldReceiver);
if (_dataUpdatedIntentReceiver != null) base.OnDestroy();
UnregisterReceiver(_dataUpdatedIntentReceiver);
base.OnDestroy();
} }
private void NotifyPluginsOnClose() private void NotifyPluginsOnClose()
@@ -1313,13 +1260,13 @@ namespace keepass2android
} }
public class WriteBinaryTask : OperationWithFinishHandler public class WriteBinaryTask : RunnableOnFinish
{ {
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly ProtectedBinary _data; private readonly ProtectedBinary _data;
private IOConnectionInfo _targetIoc; private IOConnectionInfo _targetIoc;
public WriteBinaryTask(IKp2aApp app, OnOperationFinishedHandler onOperationFinishedHandler, ProtectedBinary data, IOConnectionInfo targetIoc) : base(app, onOperationFinishedHandler) public WriteBinaryTask(Activity activity, IKp2aApp app, OnFinish onFinish, ProtectedBinary data, IOConnectionInfo targetIoc) : base(activity, onFinish)
{ {
_app = app; _app = app;
_data = data; _data = data;
@@ -1407,7 +1354,6 @@ namespace keepass2android
} }
bool isPaused = false; bool isPaused = false;
private UpdateEntryActivityBroadcastReceiver _dataUpdatedIntentReceiver;
protected override void OnPause() protected override void OnPause()
{ {
@@ -1494,8 +1440,8 @@ namespace keepass2android
Finish(); Finish();
return true; return true;
case Resource.Id.menu_delete: case Resource.Id.menu_delete:
DeleteEntry task = new DeleteEntry(App.Kp2a, Entry, DeleteEntry task = new DeleteEntry(this, App.Kp2a, Entry,
new ActionOnOperationFinished(App.Kp2a, (success, message, context) => { if (success) { RequiresRefresh(); Finish();}})); new ActionOnFinish(this, (success, message, activity) => { if (success) { RequiresRefresh(); Finish();}}));
task.Start(); task.Start();
break; break;
case Resource.Id.menu_toggle_pass: case Resource.Id.menu_toggle_pass:
@@ -1558,16 +1504,16 @@ namespace keepass2android
//save the entry: //save the entry:
ActionOnOperationFinished closeOrShowError = new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => ActionOnFinish closeOrShowError = new ActionOnFinish(this, (success, message, activity) =>
{ {
OnOperationFinishedHandler.DisplayMessage(this, message, true); OnFinish.DisplayMessage(this, message, true);
finishAction(context as EntryActivity); finishAction((EntryActivity)activity);
}); });
OperationWithFinishHandler runnable = new UpdateEntry(App.Kp2a, initialEntry, newEntry, closeOrShowError); RunnableOnFinish runnable = new UpdateEntry(this, App.Kp2a, initialEntry, newEntry, closeOrShowError);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, runnable); ProgressTask pt = new ProgressTask(App.Kp2a, this, runnable);
pt.Run(); pt.Run();
} }
@@ -1600,10 +1546,10 @@ namespace keepass2android
string url = _stringViews[urlFieldKey].Text; string url = _stringViews[urlFieldKey].Text;
if (url == null) return false; if (url == null) return false;
// Default http:// if no protocol specified // Default https:// if no protocol specified
if ((!url.Contains(":") || (url.StartsWith("www.")))) if ((!url.Contains(":") || (url.StartsWith("www."))))
{ {
url = "http://" + url; url = "https://" + url;
} }
try try
@@ -1657,7 +1603,5 @@ namespace keepass2android
imageViewerIntent.PutExtra("EntryKey", key); imageViewerIntent.PutExtra("EntryKey", key);
StartActivity(imageViewerIntent); StartActivity(imageViewerIntent);
} }
}
public IProgressUi? ProgressUi => FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container);
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,7 @@ namespace keepass2android
} }
/// <summary> /// <summary>
/// Holds the state of the EntryEditActivity. This is required to be able to keep a partially modified entry in memory /// Holds the state of the EntrryEditActivity. This is required to be able to keep a partially modified entry in memory
/// through the App variable. Serializing this state (especially the Entry/EntryInDatabase) can be a performance problem /// through the App variable. Serializing this state (especially the Entry/EntryInDatabase) can be a performance problem
/// when there are big attachements. /// when there are big attachements.
/// </summary> /// </summary>

View File

@@ -19,23 +19,23 @@ namespace keepass2android
{ {
private readonly FileFormatProvider _ffp; private readonly FileFormatProvider _ffp;
public ExportDbProcessManager(int requestCode, LifecycleAwareActivity activity, FileFormatProvider ffp) : base(requestCode, activity) public ExportDbProcessManager(int requestCode, Activity activity, FileFormatProvider ffp) : base(requestCode, activity)
{ {
_ffp = ffp; _ffp = ffp;
} }
protected override void SaveFile(IOConnectionInfo ioc) protected override void SaveFile(IOConnectionInfo ioc)
{ {
var exportDb = new ExportDatabaseActivity.ExportDb(App.Kp2a, new ActionInContextInstanceOnOperationFinished(_activity.ContextInstanceId, App.Kp2a, (success, message, context) => var exportDb = new ExportDatabaseActivity.ExportDb(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) =>
{ {
if (!success) if (!success)
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error); App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
else else
App.Kp2a.ShowMessage(context, _activity.GetString(Resource.String.export_database_successful), MessageSeverity.Info); App.Kp2a.ShowMessage(activity, _activity.GetString(Resource.String.export_database_successful), MessageSeverity.Info);
(context as Activity)?.Finish(); activity.Finish();
} }
), _ffp, ioc); ), _ffp, ioc);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, exportDb); ProgressTask pt = new ProgressTask(App.Kp2a, _activity, exportDb);
pt.Run(); pt.Run();
} }
@@ -93,13 +93,13 @@ namespace keepass2android
get { return 0; } get { return 0; }
} }
public class ExportDb : OperationWithFinishHandler public class ExportDb : RunnableOnFinish
{ {
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly FileFormatProvider _fileFormat; private readonly FileFormatProvider _fileFormat;
private IOConnectionInfo _targetIoc; private IOConnectionInfo _targetIoc;
public ExportDb(IKp2aApp app, OnOperationFinishedHandler onOperationFinishedHandler, FileFormatProvider fileFormat, IOConnectionInfo targetIoc) : base(app, onOperationFinishedHandler) public ExportDb(Activity activity, IKp2aApp app, OnFinish onFinish, FileFormatProvider fileFormat, IOConnectionInfo targetIoc) : base(activity, onFinish)
{ {
_app = app; _app = app;
this._fileFormat = fileFormat; this._fileFormat = fileFormat;

View File

@@ -12,9 +12,9 @@ namespace keepass2android
{ {
protected readonly int _requestCode; protected readonly int _requestCode;
protected readonly LifecycleAwareActivity _activity; protected readonly Activity _activity;
public FileSaveProcessManager(int requestCode, LifecycleAwareActivity activity) public FileSaveProcessManager(int requestCode, Activity activity)
{ {
_requestCode = requestCode; _requestCode = requestCode;
_activity = activity; _activity = activity;
@@ -103,7 +103,7 @@ namespace keepass2android
} }
else else
{ {
var task = new CreateNewFilename(App.Kp2a, new ActionOnOperationFinished(App.Kp2a, (success, messageOrFilename, activity) => var task = new CreateNewFilename(_activity, new ActionOnFinish(_activity, (success, messageOrFilename, activity) =>
{ {
if (!success) if (!success)
{ {
@@ -115,7 +115,7 @@ namespace keepass2android
}), filename); }), filename);
new BlockingOperationStarter(App.Kp2a, task).Run(); new ProgressTask(App.Kp2a, _activity, task).Run();
} }
return true; return true;

View File

@@ -4,12 +4,15 @@ using System.Linq;
using System.Net; using System.Net;
#if !NoNet #if !NoNet
using FluentFTP; using FluentFTP;
using static Kp2aBusinessLogic.Io.SmbFileStorage;
#endif #endif
using System.Text; using System.Text;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.Res;
using Android.OS; using Android.OS;
using Android.Preferences;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
@@ -23,6 +26,7 @@ using Keepass2android.Javafilestorage;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using KeePassLib.Utility; using KeePassLib.Utility;
namespace keepass2android namespace keepass2android
{ {
public class FileSelectHelper public class FileSelectHelper
@@ -319,7 +323,7 @@ namespace keepass2android
View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null); View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null);
if (!defaultPath.EndsWith(_schemeSeparator)) if (!defaultPath.EndsWith(_schemeSeparator))
{ {
var webdavStorage = new Keepass2android.Javafilestorage.WebDavStorage(App.Kp2a.CertificateErrorHandler); var webdavStorage = CreateWebdavStorage(activity);
var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath); var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath);
dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url; dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url;
dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username; dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username;
@@ -339,7 +343,7 @@ namespace keepass2android
string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal)); string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
if (host.Contains(_schemeSeparator) == false) if (host.Contains(_schemeSeparator) == false)
host = scheme + _schemeSeparator + host; host = scheme + _schemeSeparator + host;
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(host, user, string httpPath = CreateWebdavStorage(activity).BuildFullPath(host, user,
password); password);
onStartBrowse(httpPath); onStartBrowse(httpPath);
}); });
@@ -353,7 +357,54 @@ namespace keepass2android
#endif #endif
} }
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
private void ShowSmbDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
{
#if !EXCLUDE_JAVAFILESTORAGE && !NoNet
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.smbcredentials, null);
if (!defaultPath.EndsWith(_schemeSeparator))
{
SmbConnectionInfo ci = new SmbConnectionInfo(new IOConnectionInfo() { Path = defaultPath });
dlgContents.FindViewById<EditText>(Resource.Id.smb_url).Text = ci.GetPathWithoutCredentials();
dlgContents.FindViewById<EditText>(Resource.Id.smb_domain).Text = ci.Domain;
dlgContents.FindViewById<EditText>(Resource.Id.smb_user).Text = ci.Username;
dlgContents.FindViewById<EditText>(Resource.Id.smb_password).Text = ci.Password;
}
builder.SetView(dlgContents);
builder.SetPositiveButton(Android.Resource.String.Ok,
(sender, args) =>
{
string url = dlgContents.FindViewById<EditText>(Resource.Id.smb_url).Text;
string user = dlgContents.FindViewById<EditText>(Resource.Id.smb_user).Text;
string password = dlgContents.FindViewById<EditText>(Resource.Id.smb_password).Text;
string domain = dlgContents.FindViewById<EditText>(Resource.Id.smb_domain).Text;
string fullPath = SmbConnectionInfo.FromUrlAndCredentials(url, user, password, domain).ToPath();
onStartBrowse(fullPath);
});
builder.SetCancelable(false);
EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
builder.SetTitle(activity.GetString(Resource.String.enter_smb_login_title));
Dialog dialog = builder.Create();
dialog.Show();
#endif
}
#if !NoNet
private static WebDavStorage CreateWebdavStorage(Activity activity)
{
return new WebDavStorage(App.Kp2a.CertificateErrorHandler, App.Kp2a.WebDavChunkedUploadSize);
}
#endif
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
{ {
#if !NoNet #if !NoNet
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
@@ -479,7 +530,9 @@ namespace keepass2android
ShowFtpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); ShowFtpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
else if ((defaultPath.StartsWith("http://")) || (defaultPath.StartsWith("https://"))) else if ((defaultPath.StartsWith("http://")) || (defaultPath.StartsWith("https://")))
ShowHttpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); ShowHttpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
else if (defaultPath.StartsWith("owncloud://")) else if ((defaultPath.StartsWith("smb://")))
ShowSmbDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
else if (defaultPath.StartsWith("owncloud://"))
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud"); ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud");
else if (defaultPath.StartsWith("nextcloud://")) else if (defaultPath.StartsWith("nextcloud://"))
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "nextcloud"); ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "nextcloud");
@@ -518,7 +571,7 @@ namespace keepass2android
string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal)); string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
if (host.Contains(_schemeSeparator) == false) if (host.Contains(_schemeSeparator) == false)
host = scheme + _schemeSeparator + host; host = scheme + _schemeSeparator + host;
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user, string httpPath = CreateWebdavStorage(activity).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user,
password); password);
onStartBrowse(httpPath); onStartBrowse(httpPath);
}); });
@@ -667,10 +720,10 @@ namespace keepass2android
return true; return true;
} }
private void IocSelected(Context context, IOConnectionInfo ioc) private void IocSelected(Activity activity, IOConnectionInfo ioc)
{ {
if (OnOpen != null) if (OnOpen != null)
OnOpen(context, ioc); OnOpen(activity, ioc);
} }
public bool StartFileChooser(string defaultPath) public bool StartFileChooser(string defaultPath)
@@ -781,7 +834,7 @@ namespace keepass2android
} }
else else
{ {
var task = new CreateNewFilename(App.Kp2a, new ActionOnOperationFinished(App.Kp2a, (success, messageOrFilename, newActivity) => var task = new CreateNewFilename(activity, new ActionOnFinish(activity, (success, messageOrFilename, newActivity) =>
{ {
if (!success) if (!success)
{ {
@@ -793,7 +846,7 @@ namespace keepass2android
}), filename); }), filename);
new BlockingOperationStarter(App.Kp2a, task).Run(); new ProgressTask(App.Kp2a, activity, task).Run();
} }
} }

View File

@@ -23,6 +23,7 @@ using Android.App;
using Android.App.Admin; using Android.App.Admin;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics; using Android.Graphics;
using Android.OS; using Android.OS;
using Android.Preferences; using Android.Preferences;
@@ -46,9 +47,7 @@ namespace keepass2android
#endif #endif
{ {
public const string GeneratedPasswordKey = "keepass2android.password.generated_password"; private readonly int[] _buttonLengthButtonIds = new[] {Resource.Id.btn_length6,
private readonly int[] _buttonLengthButtonIds = new[] {Resource.Id.btn_length6,
Resource.Id.btn_length8, Resource.Id.btn_length8,
Resource.Id.btn_length12, Resource.Id.btn_length12,
Resource.Id.btn_length16, Resource.Id.btn_length16,
@@ -68,10 +67,15 @@ namespace keepass2android
Resource.Id.cb_exclude_lookalike Resource.Id.cb_exclude_lookalike
}; };
PasswordFont _passwordFont = new PasswordFont(); PasswordFont _passwordFont = new PasswordFont();
private static object _popularPasswordsLock = new object();
private static bool _popularPasswordsInitialized = false;
private ActivityDesign _design;
private ActivityDesign _design;
public GeneratePasswordActivity() public GeneratePasswordActivity()
{ {
_design = new ActivityDesign(this); _design = new ActivityDesign(this);
@@ -261,7 +265,7 @@ namespace keepass2android
EditText password = (EditText) FindViewById(Resource.Id.password_edit); EditText password = (EditText) FindViewById(Resource.Id.password_edit);
Intent intent = new Intent(); Intent intent = new Intent();
intent.PutExtra(GeneratedPasswordKey, password.Text); intent.PutExtra("keepass2android.password.generated_password", password.Text);
SetResult(KeePass.ResultOkPasswordGenerator, intent); SetResult(KeePass.ResultOkPasswordGenerator, intent);
@@ -304,6 +308,10 @@ namespace keepass2android
EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit); EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit);
txtPasswordToSet.TextChanged += (sender, args) =>
{
Task.Run(() => UpdatePasswordStrengthEstimate(txtPasswordToSet.Text));
};
_passwordFont.ApplyTo(txtPasswordToSet); _passwordFont.ApplyTo(txtPasswordToSet);
@@ -469,51 +477,76 @@ namespace keepass2android
return; return;
String password = ""; String password = "";
uint passwordBits = 0;
Task.Run(() => Task.Run(() =>
{ {
password = GeneratePassword(); password = GeneratePassword();
passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray());
RunOnUiThread(() => RunOnUiThread(() =>
{ {
EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit); EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit);
txtPassword.Text = password; txtPassword.Text = password;
var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength);
progressBar.Progress = (int)passwordBits;
progressBar.Max = 128;
Color color = new Color(196, 63, 49);
if (passwordBits > 40)
{
color = new Color(219, 152, 55);
}
if (passwordBits > 64)
{
color = new Color(96, 138, 38);
}
if (passwordBits > 100)
{
color = new Color(31, 128, 31);
}
progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color,
PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
UpdateProfileSpinnerSelection(); UpdateProfileSpinnerSelection();
}); });
}); });
} }
private void UpdatePasswordStrengthEstimate(string password)
{
lock (_popularPasswordsLock)
{
if (!_popularPasswordsInitialized)
{
using (StreamReader sr = new StreamReader(Assets.Open("MostPopularPasswords.txt")))
{
var bytes = default(byte[]);
using (var memstream = new MemoryStream())
{
sr.BaseStream.CopyTo(memstream);
bytes = memstream.ToArray();
}
PopularPasswords.Add(bytes, false);
}
}
}
uint passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray());
RunOnUiThread(() =>
{
var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength);
progressBar.Progress = (int)passwordBits;
progressBar.Max = 128;
Color color = new Color(196, 63, 49);
if (passwordBits > 40)
{
color = new Color(219, 152, 55);
}
if (passwordBits > 64)
{
color = new Color(96, 138, 38);
}
if (passwordBits > 100)
{
color = new Color(31, 128, 31);
}
progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color,
PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
});
}
private void UpdateProfileSpinnerSelection() private void UpdateProfileSpinnerSelection()
{ {
int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex(); int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex();

View File

@@ -223,9 +223,9 @@ namespace keepass2android
(o, args) => (o, args) =>
{ {
//yes //yes
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, ProgressTask pt = new ProgressTask(App.Kp2a, this,
new AddTemplateEntries(App.Kp2a, new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, new AddTemplateEntries(this, App.Kp2a, new ActionOnFinish(this,
(success, message, context) => (context as GroupActivity)?.StartAddEntry()))); (success, message, activity) => ((GroupActivity)activity)?.StartAddEntry())));
pt.Run(); pt.Run();
}, },
(o, args) => (o, args) =>
@@ -235,7 +235,7 @@ namespace keepass2android
edit.Commit(); edit.Commit();
//no //no
StartAddEntry(); StartAddEntry();
},null); },null, this);
} }
else else

View File

@@ -43,13 +43,11 @@ using keepass2android;
using KeeTrayTOTP.Libraries; using KeeTrayTOTP.Libraries;
using AndroidX.AppCompat.Widget; using AndroidX.AppCompat.Widget;
using Google.Android.Material.Dialog; using Google.Android.Material.Dialog;
using keepass2android.views;
using SearchView = AndroidX.AppCompat.Widget.SearchView; using SearchView = AndroidX.AppCompat.Widget.SearchView;
using AndroidX.Core.Content;
namespace keepass2android namespace keepass2android
{ {
public abstract class GroupBaseActivity : LockCloseActivity, IProgressUiProvider public abstract class GroupBaseActivity : LockCloseActivity
{ {
public const String KeyEntry = "entry"; public const String KeyEntry = "entry";
public const String KeyMode = "mode"; public const String KeyMode = "mode";
@@ -203,18 +201,19 @@ namespace keepass2android
new PwUuid(MemUtil.HexStringToByteArray(data.Extras.GetString(GroupEditActivity.KeyCustomIconId))); new PwUuid(MemUtil.HexStringToByteArray(data.Extras.GetString(GroupEditActivity.KeyCustomIconId)));
String strGroupUuid = data.Extras.GetString(GroupEditActivity.KeyGroupUuid); String strGroupUuid = data.Extras.GetString(GroupEditActivity.KeyGroupUuid);
GroupBaseActivity act = this; GroupBaseActivity act = this;
OperationWithFinishHandler task; Handler handler = new Handler();
RunnableOnFinish task;
if (strGroupUuid == null) if (strGroupUuid == null)
{ {
task = AddGroup.GetInstance(App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, CreateRefreshAction(), false); task = AddGroup.GetInstance(this, App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, new RefreshTask(handler, this), false);
} }
else else
{ {
PwUuid groupUuid = new PwUuid(MemUtil.HexStringToByteArray(strGroupUuid)); PwUuid groupUuid = new PwUuid(MemUtil.HexStringToByteArray(strGroupUuid));
task = new EditGroup(App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.FindGroup(groupUuid), task = new EditGroup(this, App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.FindGroup(groupUuid),
CreateRefreshAction()); new RefreshTask(handler, this));
} }
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, task); ProgressTask pt = new ProgressTask(App.Kp2a, act, task);
pt.Run(); pt.Run();
} }
@@ -275,7 +274,6 @@ namespace keepass2android
private IMenuItem searchItem; private IMenuItem searchItem;
private IMenuItem searchItemDummy; private IMenuItem searchItemDummy;
private bool isPaused; private bool isPaused;
private UpdateGroupBaseActivityBroadcastReceiver _dataUpdatedIntentReceiver;
protected override void OnResume() protected override void OnResume()
{ {
@@ -747,10 +745,9 @@ namespace keepass2android
_dataUpdatedIntentReceiver = new UpdateGroupBaseActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter();
filter.AddAction(Intents.DataUpdated);
ContextCompat.RegisterReceiver(this, _dataUpdatedIntentReceiver, filter, (int)ReceiverFlags.Exported);
SetResult(KeePass.ExitNormal); SetResult(KeePass.ExitNormal);
@@ -897,9 +894,14 @@ namespace keepass2android
RegisterInfoTextDisplay( RegisterInfoTextDisplay(
"DbReadOnly"); //this ensures that we don't show the general info texts too soon "DbReadOnly"); //this ensures that we don't show the general info texts too soon
FindViewById<TextView>(Resource.Id.dbreadonly_infotext_text).Text = var infotext_view = FindViewById<TextView>(Resource.Id.dbreadonly_infotext_text);
(GetString(Resource.String.FileReadOnlyMessagePre) + " " + if (infotext_view != null)
App.Kp2a.GetResourceString(reason.Result)); {
infotext_view.Text =
(GetString(Resource.String.FileReadOnlyMessagePre) + " " +
App.Kp2a.GetResourceString(reason.Result));
}
} }
} }
UpdateBottomBarElementVisibility(Resource.Id.dbreadonly_infotext, canShow); UpdateBottomBarElementVisibility(Resource.Id.dbreadonly_infotext, canShow);
@@ -927,14 +929,14 @@ namespace keepass2android
var moveElement = new MoveElements(elementsToMove.ToList(), Group, App.Kp2a, new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, var moveElement = new MoveElements(elementsToMove.ToList(), Group, this, App.Kp2a, new ActionOnFinish(this,
(success, message, context) => (success, message, activity) =>
{ {
(context as GroupBaseActivity)?.StopMovingElements(); ((GroupBaseActivity)activity)?.StopMovingElements();
if (!String.IsNullOrEmpty(message)) if (!String.IsNullOrEmpty(message))
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error); App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
})); }));
var progressTask = new BlockingOperationStarter(App.Kp2a, moveElement); var progressTask = new ProgressTask(App.Kp2a, this, moveElement);
progressTask.Run(); progressTask.Run();
} }
@@ -1036,13 +1038,6 @@ namespace keepass2android
} }
} }
protected override void OnDestroy()
{
UnregisterReceiver(_dataUpdatedIntentReceiver);
base.OnDestroy();
}
public override bool OnCreateOptionsMenu(IMenu menu) public override bool OnCreateOptionsMenu(IMenu menu)
{ {
@@ -1222,7 +1217,7 @@ namespace keepass2android
return true; return true;
case Resource.Id.menu_sync: case Resource.Id.menu_sync:
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc); new SyncUtil(this).SynchronizeDatabase(() => { });
return true; return true;
case Resource.Id.menu_work_offline: case Resource.Id.menu_work_offline:
@@ -1233,7 +1228,7 @@ namespace keepass2android
case Resource.Id.menu_work_online: case Resource.Id.menu_work_online:
App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false; App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false;
UpdateOfflineModeMenu(); UpdateOfflineModeMenu();
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc); new SyncUtil(this).SynchronizeDatabase(() => { });
return true; return true;
case Resource.Id.menu_open_other_db: case Resource.Id.menu_open_other_db:
AppTask.SetActivityResult(this, KeePass.ExitLoadAnotherDb); AppTask.SetActivityResult(this, KeePass.ExitLoadAnotherDb);
@@ -1303,7 +1298,51 @@ namespace keepass2android
} }
public class RefreshTask : OnFinish
{
public RefreshTask(Handler handler, GroupBaseActivity act)
: base(act, handler)
{
}
public override void Run()
{
if (Success)
{
((GroupBaseActivity)ActiveActivity)?.RefreshIfDirty();
}
else
{
DisplayMessage(ActiveActivity);
}
}
}
public class AfterDeleteGroup : OnFinish
{
public AfterDeleteGroup(Handler handler, GroupBaseActivity act)
: base(act, handler)
{
}
public override void Run()
{
if (Success)
{
((GroupBaseActivity)ActiveActivity)?.RefreshIfDirty();
}
else
{
Handler.Post(() =>
{
App.Kp2a.ShowMessage(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message, MessageSeverity.Error);
});
App.Kp2a.Lock(false);
}
}
}
public bool IsBeingMoved(PwUuid uuid) public bool IsBeingMoved(PwUuid uuid)
{ {
@@ -1374,79 +1413,6 @@ namespace keepass2android
{ {
GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup); GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup);
} }
public IProgressUi ProgressUi
{
get
{
return FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container);
}
}
public void OnDataUpdated()
{
if (Group == null || FragmentManager.IsDestroyed)
{
return;
}
var groupId = Group.Uuid;
if (!App.Kp2a.CurrentDb.GroupsById.ContainsKey(groupId))
{
Finish();
return;
}
Group = App.Kp2a.CurrentDb.GroupsById[groupId];
var fragment = FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment);
if (fragment == null)
{
throw new Exception("did not find fragment");
}
fragment.ListAdapter = new PwGroupListAdapter(this, Group);
SetGroupIcon();
SetGroupTitle();
ListAdapter?.NotifyDataSetChanged();
}
public OnOperationFinishedHandler CreateRefreshAction()
{
return new ActionInContextInstanceOnOperationFinished(
ContextInstanceId, App.Kp2a,
(success, message, context) =>
{
if (success)
{
RunOnUiThread(() =>
{
(context as GroupBaseActivity)?.RefreshIfDirty();
});
}
else
{
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
}
}
);
}
}
public class UpdateGroupBaseActivityBroadcastReceiver : BroadcastReceiver
{
private readonly GroupBaseActivity _groupBaseActivity;
public UpdateGroupBaseActivityBroadcastReceiver(GroupBaseActivity groupBaseActivity)
{
_groupBaseActivity = groupBaseActivity;
}
public override void OnReceive(Context? context, Intent? intent)
{
if (intent?.Action == Intents.DataUpdated)
{
_groupBaseActivity.OnDataUpdated();
}
}
} }
public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener
@@ -1509,12 +1475,12 @@ namespace keepass2android
{ {
return false; return false;
} }
Handler handler = new Handler();
switch (item.ItemId) switch (item.ItemId)
{ {
case Resource.Id.menu_delete: case Resource.Id.menu_delete:
DeleteMultipleItems((GroupBaseActivity)Activity, checkedItems, ((GroupBaseActivity)Activity).CreateRefreshAction(), App.Kp2a); DeleteMultipleItems((GroupBaseActivity)Activity, checkedItems, new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a);
break; break;
case Resource.Id.menu_move: case Resource.Id.menu_move:
var navMove = new NavigateToFolderAndLaunchMoveElementTask(App.Kp2a.CurrentDb, checkedItems.First().ParentGroup, checkedItems.Select(i => i.Uuid).ToList(), ((GroupBaseActivity)Activity).IsSearchResult); var navMove = new NavigateToFolderAndLaunchMoveElementTask(App.Kp2a.CurrentDb, checkedItems.First().ParentGroup, checkedItems.Select(i => i.Uuid).ToList(), ((GroupBaseActivity)Activity).IsSearchResult);
@@ -1522,10 +1488,10 @@ namespace keepass2android
break; break;
case Resource.Id.menu_copy: case Resource.Id.menu_copy:
var copyTask = new CopyEntry(App.Kp2a, (PwEntry)checkedItems.First(), var copyTask = new CopyEntry((GroupBaseActivity)Activity, App.Kp2a, (PwEntry)checkedItems.First(),
((GroupBaseActivity)Activity).CreateRefreshAction(), App.Kp2a.CurrentDb); new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a.CurrentDb);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, copyTask); ProgressTask pt = new ProgressTask(App.Kp2a, Activity, copyTask);
pt.Run(); pt.Run();
break; break;
@@ -1670,7 +1636,7 @@ namespace keepass2android
} }
public void DeleteMultipleItems(GroupBaseActivity activity, List<IStructureItem> checkedItems, OnOperationFinishedHandler onOperationFinishedHandler, Kp2aApp app) public void DeleteMultipleItems(GroupBaseActivity activity, List<IStructureItem> checkedItems, OnFinish onFinish, Kp2aApp app)
{ {
if (checkedItems.Any() == false) if (checkedItems.Any() == false)
return; return;
@@ -1701,30 +1667,30 @@ namespace keepass2android
} }
int dbIndex = 0; int dbIndex = 0;
Action<bool, string, Context> action = null; Action<bool, string, Activity> action = null;
action = (success, message, context) => action = (success, message, activeActivity) =>
{ {
if (success) if (success)
{ {
dbIndex++; dbIndex++;
if (dbIndex == itemsForDatabases.Count) if (dbIndex == itemsForDatabases.Count)
{ {
onOperationFinishedHandler.SetResult(true); onFinish.SetResult(true);
onOperationFinishedHandler.Run(); onFinish.Run();
return; return;
} }
new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key, new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key,
itemsForDatabases[dbIndex].Value, new ActionOnOperationFinished(App.Kp2a, (b, s, activity1) => action(b, s, activity1)), app) itemsForDatabases[dbIndex].Value, new ActionOnFinish(activeActivity, (b, s, activity1) => action(b, s, activity1)), app)
.Start(); .Start();
} }
else else
{ {
onOperationFinishedHandler.SetResult(false, message, true, null); onFinish.SetResult(false, message, true, null);
} }
}; };
new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key, new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key,
itemsForDatabases[dbIndex].Value, new ActionOnOperationFinished(App.Kp2a, (b, s, activity1) => action(b, s, activity1)), app) itemsForDatabases[dbIndex].Value, new ActionOnFinish(activity, (b, s, activity1) => action(b, s, activity1)), app)
.Start(); .Start();
} }

View File

@@ -25,7 +25,7 @@ using Android.Runtime;
namespace keepass2android namespace keepass2android
{ {
public abstract class LifecycleAwareActivity : AndroidX.AppCompat.App.AppCompatActivity, IContextInstanceIdProvider public abstract class LifecycleAwareActivity : AndroidX.AppCompat.App.AppCompatActivity
{ {
protected override void AttachBaseContext(Context baseContext) protected override void AttachBaseContext(Context baseContext)
{ {
@@ -84,11 +84,12 @@ namespace keepass2android
return baseRes; return baseRes;
} }
public Action? OnResumeListener { get; set; }
protected override void OnResume() protected override void OnResume()
{ {
base.OnResume(); base.OnResume();
App.Kp2a.PerformPendingActions(_instanceId); OnResumeListener?.Invoke();
Kp2aLog.Log(ClassName + ".OnResume " + ID); Kp2aLog.Log(ClassName + ".OnResume " + ID);
if (App.Kp2a.CurrentDb == null) if (App.Kp2a.CurrentDb == null)
@@ -103,38 +104,26 @@ namespace keepass2android
protected override void OnStart() protected override void OnStart()
{ {
App.Kp2a.ActiveContext = this; ProgressTask.SetNewActiveActivity(this);
base.OnStart(); base.OnStart();
Kp2aLog.Log(ClassName + ".OnStart" + " " + ID); Kp2aLog.Log(ClassName + ".OnStart" + " " + ID);
} }
const string ID_KEY = "kp2a_context_instance_id";
const int InvalidId = -1;
private int _instanceId;
protected override void OnCreate(Bundle bundle) protected override void OnCreate(Bundle bundle)
{ {
base.OnCreate(bundle); base.OnCreate(bundle);
_instanceId = bundle?.GetInt(ID_KEY, InvalidId) ?? InvalidId;
if (_instanceId == InvalidId)
{
_instanceId = _nextContextInstanceId++;
}
OnCreateListener?.Invoke(bundle); OnCreateListener?.Invoke(bundle);
Kp2aLog.Log(ClassName + ".OnCreate" + " " + ID + " (instance=" + _instanceId +")"); Kp2aLog.Log(ClassName + ".OnCreate" + " " + ID);
Kp2aLog.Log(ClassName + ":apptask=" + Intent.GetStringExtra("KP2A_APP_TASK_TYPE") + " " + ID); Kp2aLog.Log(ClassName + ":apptask=" + Intent.GetStringExtra("KP2A_APP_TASK_TYPE") + " " + ID);
} }
protected override void OnDestroy() protected override void OnDestroy()
{ {
base.OnDestroy(); base.OnDestroy();
Kp2aLog.Log(ClassName + ".OnDestroy " + IsFinishing.ToString() + " " + ID); Kp2aLog.Log(ClassName + ".OnDestroy" + IsFinishing.ToString() + " " + ID);
} }
protected override void OnPause() protected override void OnPause()
@@ -147,18 +136,14 @@ namespace keepass2android
{ {
base.OnStop(); base.OnStop();
Kp2aLog.Log(ClassName + ".OnStop" + " " + ID); Kp2aLog.Log(ClassName + ".OnStop" + " " + ID);
ProgressTask.RemoveActiveActivity(this);
} }
protected override void OnSaveInstanceState(Bundle outState) protected override void OnSaveInstanceState(Bundle outState)
{ {
base.OnSaveInstanceState(outState); base.OnSaveInstanceState(outState);
outState.PutInt(ID_KEY, _instanceId);
OnSaveInstanceStateListener?.Invoke(outState); OnSaveInstanceStateListener?.Invoke(outState);
} }
static int _nextContextInstanceId = 0;
public int ContextInstanceId => _instanceId;
} }
} }

View File

@@ -38,7 +38,7 @@ namespace keepass2android
protected const string NoLockCheck = "NO_LOCK_CHECK"; protected const string NoLockCheck = "NO_LOCK_CHECK";
protected IOConnectionInfo _ioc; protected IOConnectionInfo _ioc;
private BroadcastReceiver _lockCloseIntentReceiver; private BroadcastReceiver _intentReceiver;
private ActivityDesign _design; private ActivityDesign _design;
public LockCloseActivity() public LockCloseActivity()
@@ -66,11 +66,11 @@ namespace keepass2android
if (Intent.GetBooleanExtra(NoLockCheck, false)) if (Intent.GetBooleanExtra(NoLockCheck, false))
return; return;
_lockCloseIntentReceiver = new LockCloseActivityBroadcastReceiver(this); _intentReceiver = new LockCloseActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.AddAction(Intents.DatabaseLocked); filter.AddAction(Intents.DatabaseLocked);
filter.AddAction(Intent.ActionScreenOff); filter.AddAction(Intent.ActionScreenOff);
ContextCompat.RegisterReceiver(this, _lockCloseIntentReceiver, filter, (int)ReceiverFlags.Exported); ContextCompat.RegisterReceiver(this, _intentReceiver, filter, (int)ReceiverFlags.Exported);
} }
protected override void OnDestroy() protected override void OnDestroy()
@@ -79,7 +79,7 @@ namespace keepass2android
{ {
try try
{ {
UnregisterReceiver(_lockCloseIntentReceiver); UnregisterReceiver(_intentReceiver);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -266,7 +266,6 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" />
<uses-permission android:name="keepass2android.keepass2android_debug.permission.KP2aInternalFileBrowsing" /> <uses-permission android:name="keepass2android.keepass2android_debug.permission.KP2aInternalFileBrowsing" />

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="206" android:versionCode="222"
android:versionName="1.12-r5" android:versionName="1.13-r0"
package="keepass2android.keepass2android" package="keepass2android.keepass2android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"> android:installLocation="auto">
<queries> <queries>
@@ -46,13 +46,11 @@
<permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher" android:label="KP2A entry search" android:name="keepass2android.keepass2android.permission.KP2aInternalSearch" android:protectionLevel="signature" /> <permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher" android:label="KP2A entry search" android:name="keepass2android.keepass2android.permission.KP2aInternalSearch" android:protectionLevel="signature" />
<permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android.permission.Kp2aChooseAutofill" android:protectionLevel="signature" /> <permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
<application android:label="keepass2android" <application android:label="keepass2android"
android:icon="@mipmap/ic_launcher_online" android:icon="@mipmap/ic_launcher_online"
android:roundIcon="@mipmap/ic_launcher_online_round" android:roundIcon="@mipmap/ic_launcher_online_round"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
>
>
<meta-data <meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES" android:name="com.google.mlkit.vision.DEPENDENCIES"
@@ -107,16 +105,15 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="keepass2android.softkeyboard.InputLanguageSelection" <activity android:name="keepass2android.softkeyboard.InputLanguageSelection"
android:exported="true"> android:exported="true">
<!-- android:label="@string/language_selection_title" TODO -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/> <action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize" android:exported="true"> <activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter android:label="@string/app_name"> <intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@@ -278,7 +275,6 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" />
<uses-permission android:name="keepass2android.keepass2android.permission.KP2aInternalFileBrowsing" /> <uses-permission android:name="keepass2android.keepass2android.permission.KP2aInternalFileBrowsing" />

View File

@@ -1,24 +1,27 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="200" android:versionCode="222"
android:versionName="1.11-r0" android:versionName="1.13-r0"
package="keepass2android.keepass2android_nonet" package="keepass2android.keepass2android_nonet"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"> android:installLocation="auto">
<queries> <queries>
<!-- Specific intents and packages we query for (required since Android 11) --> <!-- Specific intents and packages we query for (required since Android 11) -->
<package android:name="keepass2android.plugin.keyboardswap2" /> <package android:name="keepass2android.plugin.keyboardswap2" />
<package android:name="keepass2android.AncientIconSet" /> <package android:name="keepass2android.AncientIconSet" />
<package android:name="com.dropbox.android" /> <package android:name="keepass2android.plugin.qr" />
<package android:name="it.andreacioni.kp2a.plugin.keelink" />
<package android:name="com.inputstick.apps.kp2aplugin" />
<package android:name="com.dropbox.android" />
<intent> <intent>
<action android:name="android.intent.action.OPEN_DOCUMENT" /> <action android:name="android.intent.action.OPEN_DOCUMENT" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
</intent> </intent>
<intent> <intent>
<action android:name="android.intent.action.GET_DOCUMENT" /> <action android:name="android.intent.action.GET_DOCUMENT" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
</intent> </intent>
@@ -36,59 +39,57 @@
</intent> </intent>
<intent> <intent>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
</intent> </intent>
</queries> </queries>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher_offline" android:label="KP2A entry search" android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" android:protectionLevel="signature" /> <permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher_offline" android:label="KP2A entry search" android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" android:protectionLevel="signature" />
<permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher_offline" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_nonet.permission.Kp2aChooseAutofill" android:protectionLevel="signature" /> <permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher_offline" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_nonet.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
<application <application android:label="keepass2android"
android:label="keepass2android" android:icon="@mipmap/ic_launcher_offline"
android:icon="@mipmap/ic_launcher_offline" android:roundIcon="@mipmap/ic_launcher_offline_round"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
> >
<meta-data <meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES" android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode_ui"/> android:value="barcode_ui"/>
<uses-library <uses-library
android:name="org.apache.http.legacy" android:name="org.apache.http.legacy"
android:required="false"/> android:required="false"/>
<provider android:name="group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.localfile" android:exported="false" /> <provider android:name="group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.localfile" android:exported="false" />
<provider android:name="group.pals.android.lib.ui.filechooser.providers.history.HistoryProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.history" android:exported="false" /> <provider android:name="group.pals.android.lib.ui.filechooser.providers.history.HistoryProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.history" android:exported="false" />
<activity android:name="group.pals.android.lib.ui.filechooser.FileChooserActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:screenOrientation="user" android:theme="@style/Afc.Theme.Light"> <activity android:name="group.pals.android.lib.ui.filechooser.FileChooserActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:screenOrientation="user" android:theme="@style/Afc.Theme.Light">
</activity> </activity>
<service android:name="keepass2android.softkeyboard.KP2AKeyboard" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true"> <service android:name="keepass2android.softkeyboard.KP2AKeyboard" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.view.InputMethod" /> <action android:name="android.view.InputMethod" />
</intent-filter> </intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" /> <meta-data android:name="android.view.im" android:resource="@xml/method" />
</service> </service>
<activity android:name="keepass2android.softkeyboard.LatinIMESettings" android:label="@string/english_ime_settings" android:exported="true"> <activity android:name="keepass2android.softkeyboard.LatinIMESettings" android:label="@string/english_ime_settings" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="keepass2android.softkeyboard.LatinIMESettings" /> <action android:name="keepass2android.softkeyboard.LatinIMESettings" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="keepass2android.softkeyboard.InputLanguageSelection" <activity android:name="keepass2android.softkeyboard.InputLanguageSelection"
android:label="@string/language_selection_title" android:exported="true">
android:exported="true"> <intent-filter>
<intent-filter> <action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN"/> <action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/>
<action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT" /> </intent-filter>
</intent-filter> </activity>
</activity> <activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize"
android:exported="true">
<activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter android:label="@string/app_name"> <intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@@ -98,7 +99,7 @@
<data android:host="*" /> <data android:host="*" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="kp2a.action.SelectCurrentDbActivity" /> <action android:name="kp2a.action.SelectCurrentDbActivity" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
@@ -112,7 +113,7 @@
<data android:mimeType="application/*" /> <data android:mimeType="application/*" />
</intent-filter> </intent-filter>
<!-- intent filter for opening database files <!-- intent filter for opening database files
Note that this stopped working nicely with Android 7, see e.g. https://stackoverflow.com/a/26635162/292233 Note that this stopped working nicely with Android 7, see e.g. https://stackoverflow.com/a/26635162/292233
KP2A was using KP2A was using
<data android:scheme="content" /> <data android:scheme="content" />
@@ -126,7 +127,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
--> -->
<!-- This intent filter is for apps which use content with a URI containing the extension but no specific mimeType, e.g. ASTRO file manager --> <!-- This intent filter is for apps which use content with a URI containing the extension but no specific mimeType, e.g. ASTRO file manager -->
<intent-filter android:label="@string/app_name"> <intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -168,7 +169,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
</intent-filter> </intent-filter>
<!-- This intent filter is for apps which use content with a URI not containing the extension but at least specify mimeType=application/octet-stream, e.g. GoogleDrive or FolderSync --> <!-- This intent filter is for apps which use content with a URI not containing the extension but at least specify mimeType=application/octet-stream, e.g. GoogleDrive or FolderSync -->
<intent-filter android:label="@string/app_name"> <intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
@@ -218,24 +219,15 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdb" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdb" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter android:label="@string/kp2a_findUrl">
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.DEFAULT" />
<data <data android:mimeType="text/plain" />
android:scheme="https" </intent-filter>
android:host="my.yubico.com" <intent-filter>
android:pathPrefix="/neo"/> <action android:name="keepass2android.ACTION_START_WITH_TASK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
<intent-filter android:label="@string/kp2a_findUrl">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="keepass2android.ACTION_START_WITH_TASK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@@ -244,27 +236,28 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<data android:host="totp"/> <data android:host="totp"/>
<data android:host="hotp"/> <data android:host="hotp"/>
</intent-filter> </intent-filter>
</activity> </activity>
<uses-library android:required="false" android:name="com.sec.android.app.multiwindow" /> <uses-library android:required="false" android:name="com.sec.android.app.multiwindow" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" /> <meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" /> <meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" /> <meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="426.0dip" /> <meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="426.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="360.0dip" /> <meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="360.0dip" />
</application> </application>
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalFileBrowsing" />
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalFileBrowsing" /> <!-- Samsung Pass permission -->
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" /> <uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />
</manifest> </manifest>

View File

@@ -66,7 +66,6 @@ using Exception = System.Exception;
using String = System.String; using String = System.String;
using Toolbar = AndroidX.AppCompat.Widget.Toolbar; using Toolbar = AndroidX.AppCompat.Widget.Toolbar;
using AndroidX.Core.Content; using AndroidX.Core.Content;
using Google.Android.Material.Snackbar;
namespace keepass2android namespace keepass2android
{ {
@@ -132,7 +131,8 @@ namespace keepass2android
ISharedPreferences _prefs; ISharedPreferences _prefs;
private bool _starting; private bool _starting;
private OtpInfo _otpInfo; private bool _resumeCompleted;
private OtpInfo _otpInfo;
private IOConnectionInfo _otpAuxIoc; private IOConnectionInfo _otpAuxIoc;
private ChallengeInfo _chalInfo; private ChallengeInfo _chalInfo;
private byte[] _challengeSecret; private byte[] _challengeSecret;
@@ -222,7 +222,6 @@ namespace keepass2android
//StackBaseActivity will launch the next activity //StackBaseActivity will launch the next activity
Intent data = new Intent(); Intent data = new Intent();
data.PutExtra("ioc", IOConnectionInfo.SerializeToString(_ioConnection)); data.PutExtra("ioc", IOConnectionInfo.SerializeToString(_ioConnection));
data.PutExtra("requiresSubsequentSync", _lastLoadOperation?.RequiresSubsequentSync == true);
SetResult(Result.Ok, data); SetResult(Result.Ok, data);
@@ -421,8 +420,14 @@ namespace keepass2android
try try
{ {
var iocAux = GetDefaultAuxLocation(); var iocAux = GetDefaultAuxLocation();
LoadFile(iocAux); LoadFile(iocAux);
}
if (Activity._chalInfo == null)
{
throw new Java.Lang.Exception("Failed to load challenge aux file");
}
}
catch (Exception e) catch (Exception e)
{ {
//this can happen e.g. if the file storage does not support GetParentPath //this can happen e.g. if the file storage does not support GetParentPath
@@ -802,8 +807,6 @@ namespace keepass2android
_password = i.GetStringExtra(KeyPassword) ?? ""; _password = i.GetStringExtra(KeyPassword) ?? "";
if (!KeyProviderTypes.Any()) if (!KeyProviderTypes.Any())
{ {
SetKeyProviderFromString(LoadKeyProviderStringForIoc(_ioConnection.Path)); SetKeyProviderFromString(LoadKeyProviderStringForIoc(_ioConnection.Path));
} }
@@ -1256,7 +1259,7 @@ namespace keepass2android
case 6: case 6:
KeyProviderTypes.Add(KeyProviders.ChallengeXC); KeyProviderTypes.Add(KeyProviders.ChallengeXC);
break; break;
case 7: case 7:
//don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!) //don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!)
Kp2aLog.Log("key file length before: " + _keyFile?.Length); Kp2aLog.Log("key file length before: " + _keyFile?.Length);
_keyFile = (FindViewById(Resource.Id.label_keyfilename).Tag ?? "").ToString(); _keyFile = (FindViewById(Resource.Id.label_keyfilename).Tag ?? "").ToString();
@@ -1420,6 +1423,8 @@ namespace keepass2android
if (cbQuickUnlock == null) if (cbQuickUnlock == null)
throw new NullPointerException("cpQuickUnlock"); throw new NullPointerException("cpQuickUnlock");
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked); App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase =
(((KeyguardManager)GetSystemService(Context.KeyguardService)!)!).IsDeviceSecure;
if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline)) if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline))
{ {
@@ -1441,22 +1446,15 @@ namespace keepass2android
MakePasswordMaskedOrVisible(); MakePasswordMaskedOrVisible();
Handler handler = new Handler(); Handler handler = new Handler();
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection); OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
LoadDb loadOperation = (KeyProviderTypes.Contains(KeyProviders.Otp)) LoadDb task = (KeyProviderTypes.Contains(KeyProviders.Otp))
? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), ? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(),
onOperationFinishedHandler, this, true, _makeCurrent) onFinish, this, true, _makeCurrent)
: new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onOperationFinishedHandler,true, _makeCurrent); : new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onFinish,true, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use _loadDbFileTask = null; // prevent accidental re-use
_lastLoadOperation = loadOperation; new ProgressTask(App.Kp2a, this, task).Run();
}
//Don't use BlockingOperationStarter as that would cancel running operations.
//This is bad when used with AutoOpen: we might get here when one database has loaded and is now synced in the background.
//We don't want to cancel this.
OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);
}
catch (Exception e) catch (Exception e)
{ {
Kp2aLog.LogUnexpectedError(new Exception("cannot load database: "+e + ", c: " + (compositeKey != null) + (_ioConnection != null) + (_keyFile != null), e)); Kp2aLog.LogUnexpectedError(new Exception("cannot load database: "+e + ", c: " + (compositeKey != null) + (_ioConnection != null) + (_keyFile != null), e));
@@ -1580,7 +1578,7 @@ namespace keepass2android
} }
private bool hasRequestedKeyboardActivation = false; private bool hasRequestedKeyboardActivation = false;
private LoadDb _lastLoadOperation;
protected override void OnStart() protected override void OnStart()
{ {
@@ -1657,60 +1655,65 @@ namespace keepass2android
if (intent != null) if (intent != null)
{ {
if (intent.HasExtra(Intents.OtpExtraKey)) if (intent.HasExtra(Intents.OtpExtraKey))
{ {
string otp = intent.GetStringExtra(Intents.OtpExtraKey); string otp = intent.GetStringExtra(Intents.OtpExtraKey);
_keepPasswordInOnResume = true; _keepPasswordInOnResume = true;
if (KeyProviderTypes.Contains(KeyProviders.Otp)) if (KeyProviderTypes.Contains(KeyProviders.Otp))
{ {
if (_otpInfo == null) if (_otpInfo == null)
{ {
//Entering OTPs not yet initialized: //Entering OTPs not yet initialized:
_pendingOtps.Add(otp); _pendingOtps.Add(otp);
UpdateKeyProviderUiState(); UpdateKeyProviderUiState();
} }
else else
{ {
//Entering OTPs is initialized. Write OTP into first empty field: //Entering OTPs is initialized. Write OTP into first empty field:
bool foundEmptyField = false; bool foundEmptyField = false;
foreach (int otpId in _otpTextViewIds) foreach (int otpId in _otpTextViewIds)
{ {
EditText otpEdit = FindViewById<EditText>(otpId); EditText otpEdit = FindViewById<EditText>(otpId);
if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text)) if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text))
{ {
otpEdit.Text = otp; otpEdit.Text = otp;
foundEmptyField = true; foundEmptyField = true;
break; break;
} }
} }
//did we find a field? //did we find a field?
if (!foundEmptyField) if (!foundEmptyField)
{ {
App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_no_space), MessageSeverity.Error); App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_no_space), MessageSeverity.Error);
} }
} }
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner); Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp) if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp)
{ {
passwordModeSpinner.SetSelection((int)KeyProviders.Otp); passwordModeSpinner.SetSelection((int)KeyProviders.Otp);
} }
} }
else else
{
//assume the key should be used as static password
FindViewById<EditText>(Resource.Id.password_edit).Text += otp;
}
}
else
{
// if the activity is launched twice and the first initialization hasn't even finished, we cannot
// reset the state and re-initialize the activity.
// This can happen with autofill in some cases (#2869)
if (_resumeCompleted)
{ {
//assume the key should be used as static password ResetState();
FindViewById<EditText>(Resource.Id.password_edit).Text += otp; GetIocFromLaunchIntent(intent);
InitializeAfterSetIoc();
OnStart();
} }
}
}
else
{
ResetState();
GetIocFromLaunchIntent(intent);
InitializeAfterSetIoc();
OnStart();
}
} }
} }
@@ -1749,179 +1752,150 @@ namespace keepass2android
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
protected override void OnResume() protected override void OnResume()
{ {
base.OnResume(); base.OnResume();
_activityDesign.ReapplyTheme(); _activityDesign.ReapplyTheme();
Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " + Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " +
_performingLoad); _performingLoad);
CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline); CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline);
App.Kp2a.OfflineMode = App.Kp2a.OfflineMode =
cbOfflineMode.Checked = cbOfflineMode.Checked =
App.Kp2a App.Kp2a
.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings .OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings
LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container);
CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background)!; var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
cbSyncInBackground.Checked = App.Kp2a.SyncInBackgroundPreference; if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
UpdateInternalCacheCheckboxesVisibility();
View killButton = FindViewById(Resource.Id.kill_app);
if (PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.show_kill_app_key), false))
{
killButton.Click += (sender, args) =>
{
_killOnDestroy = true;
SetResult(Result.Canceled);
Finish();
};
killButton.Visibility = ViewStates.Visible;
}
else
{
killButton.Visibility = ViewStates.Gone;
}
TryGetOtpFromClipboard();
if (!_keepPasswordInOnResume)
{
if (
_lastOnPauseTime <
DateTime.Now -
TimeSpan.FromSeconds(
5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app)
&&
PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true))
{
ClearEnteredPassword();
}
}
_keepPasswordInOnResume = false;
MakePasswordMaskedOrVisible();
UpdateOkButtonState();
if (KeyProviderTypes.Contains(KeyProviders.Challenge))
{
FindViewById(Resource.Id.otpInitView).Visibility =
_challengeSecret == null ? ViewStates.Visible : ViewStates.Gone;
}
/*
Snackbar snackbar = Snackbar
.Make(FindViewById(Resource.Id.main_content),
"snack snack snack snack snack snack snack snack snack snack snack snack snack snack snacksnack snack snacksnack snack snacksnack snack snack snack snack snack snack snack snack snack snack snack snack snack snack snacksnack snack snacksnack snack snacksnack snack snack snack snack snacksnack snack snack ",
Snackbar.LengthLong);
snackbar.SetTextMaxLines(5);
snackbar.SetBackgroundTint(GetColor(Resource.Color.md_theme_secondaryContainer));
snackbar.SetTextColor(GetColor(Resource.Color.md_theme_onSecondaryContainer));
snackbar.SetAction("dismiss",
view => snackbar.SetBackgroundTint(GetColor(Resource.Color.md_theme_surfaceContainer)));
snackbar.Show();
new Handler().PostDelayed(() =>
{
Snackbar snackbar2 = Snackbar
.Make(FindViewById(Resource.Id.main_content), "snack snack snack ",
Snackbar.LengthLong);
snackbar2.SetTextMaxLines(5);
snackbar2.SetBackgroundTint(GetColor(Resource.Color.md_theme_errorContainer));
snackbar2.SetTextColor(GetColor(Resource.Color.md_theme_onErrorContainer));
snackbar2.Show();
}, 1500);
new Handler().PostDelayed(() =>
{
Snackbar snackbar2 = Snackbar
.Make(FindViewById(Resource.Id.main_content), "snack snack warn ",
Snackbar.LengthLong);
snackbar2.SetTextMaxLines(5);
snackbar2.SetBackgroundTint(GetColor(Resource.Color.md_theme_inverseSurface));
snackbar2.SetTextColor(GetColor(Resource.Color.md_theme_inverseOnSurface));
snackbar2.Show();
}, 2500);*/
//use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult)
//use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!)
if ( !IsFinishing && !_performingLoad)
{ {
offlineModeContainer.Visibility = ViewStates.Visible;
}
else
{
offlineModeContainer.Visibility = ViewStates.Gone;
App.Kp2a.OfflineMode = false;
}
// OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must
// be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult)
if (_starting)
{
_starting = false;
//database not yet loaded. View killButton = FindViewById(Resource.Id.kill_app);
if (PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.show_kill_app_key), false))
{
killButton.Click += (sender, args) =>
{
_killOnDestroy = true;
SetResult(Result.Canceled);
Finish();
//check if pre-loading is enabled but wasn't started yet: };
if (_loadDbFileTask == null && killButton.Visibility = ViewStates.Visible;
_prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true))
{ }
// Create task to kick off file loading while the user enters the password else
_loadDbFileTask = Task.Factory.StartNew(PreloadDbFile); {
_loadDbTaskOffline = App.Kp2a.OfflineMode; killButton.Visibility = ViewStates.Gone;
} }
}
TryGetOtpFromClipboard();
if (!_keepPasswordInOnResume)
{
if (
_lastOnPauseTime <
DateTime.Now -
TimeSpan.FromSeconds(
5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app)
&&
PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true))
{
ClearEnteredPassword();
}
} }
if (compositeKeyForImmediateLoad != null)
{
//reload the database (without most other stuff performed in PerformLoadDatabase.
// We're assuming that the db file (and if appropriate also the key file) are still available
// and there's no need to re-init the file storage. if it is, loading will fail and the user has
// to retry with typing the full password, but that's intended to avoid showing the password to a
// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
Handler handler = new Handler();
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true;
LoadDb loadOperation = new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onOperationFinishedHandler, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
_lastLoadOperation = loadOperation;
OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
}
else
{
bool showKeyboard = true;
EditText pwd = (EditText) FindViewById(Resource.Id.password_edit); _keepPasswordInOnResume = false;
pwd.PostDelayed(() =>
{ MakePasswordMaskedOrVisible();
InputMethodManager keyboard = (InputMethodManager) GetSystemService(InputMethodService);
if (showKeyboard) UpdateOkButtonState();
{
pwd.RequestFocus(); if (KeyProviderTypes.Contains(KeyProviders.Challenge))
keyboard.ShowSoftInput(pwd, 0); {
} FindViewById(Resource.Id.otpInitView).Visibility =
else _challengeSecret == null ? ViewStates.Visible : ViewStates.Gone;
keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly); }
}, 50);
} //use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult)
//use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!)
if (!IsFinishing && !_performingLoad)
{
// OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must
// be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult)
if (_starting)
{
_starting = false;
//database not yet loaded.
//check if pre-loading is enabled but wasn't started yet:
if (_loadDbFileTask == null &&
_prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true))
{
// Create task to kick off file loading while the user enters the password
_loadDbFileTask = Task.Factory.StartNew(PreloadDbFile);
_loadDbTaskOffline = App.Kp2a.OfflineMode;
}
}
}
if (compositeKeyForImmediateLoad != null)
{
//reload the database (without most other stuff performed in PerformLoadDatabase.
// We're assuming that the db file (and if appropriate also the key file) are still available
// and there's no need to re-init the file storage. if it is, loading will fail and the user has
// to retry with typing the full password, but that's intended to avoid showing the password to a
// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
Handler handler = new Handler();
OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true;
LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onFinish, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new ProgressTask(App.Kp2a, this, task).Run();
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
}
else
{
bool showKeyboard = true;
EditText pwd = (EditText)FindViewById(Resource.Id.password_edit);
pwd.PostDelayed(() =>
{
InputMethodManager keyboard = (InputMethodManager)GetSystemService(InputMethodService);
if (showKeyboard)
{
pwd.RequestFocus();
keyboard.ShowSoftInput(pwd, 0);
}
else
keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly);
}, 50);
}
_resumeCompleted = true;
} }
private void TryGetOtpFromClipboard() private void TryGetOtpFromClipboard()
@@ -2044,37 +2018,9 @@ namespace keepass2android
App.Kp2a.OfflineModePreference = App.Kp2a.OfflineMode = args.IsChecked; App.Kp2a.OfflineModePreference = App.Kp2a.OfflineMode = args.IsChecked;
}; };
CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background); }
cbSyncInBackground.CheckedChange += (sender, args) =>
{
App.Kp2a.SyncInBackgroundPreference = args.IsChecked;
UpdateInternalCacheCheckboxesVisibility();
}; private String LoadKeyProviderStringForIoc(String filename) {
}
private void UpdateInternalCacheCheckboxesVisibility()
{
LinearLayout syncInBackgroundContainer = FindViewById<LinearLayout>(Resource.Id.sync_in_background_container)!;
LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container)!;
var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
{
syncInBackgroundContainer.Visibility = ViewStates.Visible;
offlineModeContainer.Visibility =
App.Kp2a.SyncInBackgroundPreference ? ViewStates.Gone : ViewStates.Visible;
}
else
{
syncInBackgroundContainer.Visibility = offlineModeContainer.Visibility = ViewStates.Gone;
App.Kp2a.OfflineMode = false;
}
}
private String LoadKeyProviderStringForIoc(String filename) {
if ( _rememberKeyfile ) { if ( _rememberKeyfile ) {
string keyfile = App.Kp2a.FileDbHelper.GetKeyFileForFile(filename); string keyfile = App.Kp2a.FileDbHelper.GetKeyFileForFile(filename);
if (String.IsNullOrEmpty(keyfile)) if (String.IsNullOrEmpty(keyfile))
@@ -2142,11 +2088,11 @@ namespace keepass2android
Finish(); Finish();
} }
private class AfterLoad : OnOperationFinishedHandler { private class AfterLoad : OnFinish {
readonly PasswordActivity _act; readonly PasswordActivity _act;
private readonly IOConnectionInfo _ioConnection; private readonly IOConnectionInfo _ioConnection;
public AfterLoad(Handler handler, PasswordActivity act, IOConnectionInfo ioConnection):base(App.Kp2a, handler) public AfterLoad(Handler handler, PasswordActivity act, IOConnectionInfo ioConnection):base(act, handler)
{ {
_act = act; _act = act;
_ioConnection = ioConnection; _ioConnection = ioConnection;
@@ -2253,7 +2199,7 @@ namespace keepass2android
if (!Success) if (!Success)
_act.InitFingerprintUnlock(); _act.InitFingerprintUnlock();
_act._lastLoadOperation = null;
_act._performingLoad = false; _act._performingLoad = false;
} }
@@ -2287,7 +2233,7 @@ namespace keepass2android
private readonly PasswordActivity _act; private readonly PasswordActivity _act;
public SaveOtpAuxFileAndLoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler, PasswordActivity act, bool updateLastUsageTimestamp, bool makeCurrent) : base(app, ioc, databaseData, compositeKey, keyfileOrProvider, operationFinishedHandler,updateLastUsageTimestamp,makeCurrent) public SaveOtpAuxFileAndLoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, string keyfileOrProvider, OnFinish finish, PasswordActivity act, bool updateLastUsageTimestamp, bool makeCurrent) : base(act, app, ioc, databaseData, compositeKey, keyfileOrProvider, finish,updateLastUsageTimestamp,makeCurrent)
{ {
_act = act; _act = act;
} }

View File

@@ -25,6 +25,7 @@ using Android.Widget;
using Android.Content.PM; using Android.Content.PM;
using KeePassLib.Keys; using KeePassLib.Keys;
using Android.Preferences; using Android.Preferences;
using Android.Provider;
using Android.Runtime; using Android.Runtime;
using Android.Views.InputMethods; using Android.Views.InputMethods;
@@ -162,6 +163,29 @@ namespace keepass2android
if (bundle != null) if (bundle != null)
numFailedAttempts = bundle.GetInt(NumFailedAttemptsKey, 0); numFailedAttempts = bundle.GetInt(NumFailedAttemptsKey, 0);
FindViewById(Resource.Id.QuickUnlock_buttonEnableLock).Click += (object sender, EventArgs e) =>
{
Intent intent = new Intent(Settings.ActionSecuritySettings);
StartActivity(intent);
};
FindViewById(Resource.Id.QuickUnlock_buttonCloseDb).Click += (object sender, EventArgs e) =>
{
App.Kp2a.Lock(false);
};
if (App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase == false)
{
FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Visible;
}
else
{
FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Gone;
}
} }
@@ -339,10 +363,10 @@ namespace keepass2android
if (PreferenceManager.GetDefaultSharedPreferences(this) if (PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.SyncAfterQuickUnlock_key), false)) .GetBoolean(GetString(Resource.String.SyncAfterQuickUnlock_key), false))
{ {
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc); new SyncUtil(this).SynchronizeDatabase(Finish);
} }
else
Finish(); Finish();

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -79,19 +79,20 @@ android:paddingRight="16dp"
android:paddingTop="16dp"> android:paddingTop="16dp">
<TextView
android:id="@+id/QuickUnlock_label"
android:text="@string/QuickUnlock_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/filename_label"
android:textSize="14sp"
/>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal"
android:id="@+id/QuickUnlockForm">
<TextView
android:id="@+id/QuickUnlock_label"
android:text="@string/QuickUnlock_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/filename_label"
android:textSize="14sp"
/>
<EditText <EditText
android:inputType="textPassword" android:inputType="textPassword"
@@ -121,6 +122,60 @@ android:paddingRight="16dp"
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/md_theme_secondaryContainer"
android:id="@+id/QuickUnlockBlocked"
android:padding="16dp"
android:layout_gravity="center">
<TextView
android:id="@+id/quick_unlock_blocked_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_based_quick_unlock_not_available"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:paddingBottom="8dp"/>
<TextView
android:id="@+id/alert_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_based_quick_unlock_not_available_text"
android:textSize="16sp"
android:paddingBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/QuickUnlock_buttonEnableLock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:backgroundTint="@color/md_theme_secondary"
android:textColor="@android:color/white"
android:text="@string/enable_screen_lock"
android:fontFamily="sans-serif-medium" />
<Button
android:id="@+id/QuickUnlock_buttonCloseDb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:backgroundTint="@color/md_theme_secondary"
android:textColor="@android:color/white"
android:fontFamily="sans-serif-medium"
android:text="@string/QuickUnlock_lockButton" />
</LinearLayout>
</LinearLayout>
<View <View
android:id="@+id/spacing" android:id="@+id/spacing"

View File

@@ -1,65 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/background_ops_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/md_theme_surfaceVariant"
android:orientation="vertical"
>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:id="@+id/background_ops_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="3dp"
android:text="" />
<TextView
android:id="@+id/background_ops_submessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="3dp"
android:textSize="12sp"
android:text="" />
</LinearLayout>
<Button
android:id="@+id/cancel_background"
style="?attr/materialIconButtonStyle"
app:icon="@drawable/baseline_close_24"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dip"
android:layout_margin="6dip"
android:layout_weight="0"
/>
</LinearLayout>
</LinearLayout>

View File

@@ -43,6 +43,7 @@
style="@style/EntryEditSingleLine_EditText" style="@style/EntryEditSingleLine_EditText"
android:layout_marginRight="0dip" /> android:layout_marginRight="0dip" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
>
<CheckBox <CheckBox
android:id="@+id/protection" android:id="@+id/protection"

View File

@@ -75,11 +75,6 @@ android:layout_height="wrap_content">
android:layout_marginRight="0dip" android:layout_marginRight="0dip"
android:visibility="gone" android:visibility="gone"
/> />
<CheckBox
android:id="@+id/protection"
android:layout_width="0dip"
android:layout_height="0dip"
android:visibility="gone"/>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>

Some files were not shown because too many files have changed in this diff Show More