Compare commits

..

28 Commits

Author SHA1 Message Date
Philipp Crocoll
c3c9c8610d remove debug logging 2025-06-03 16:00:24 +02:00
Philipp Crocoll
9c2d50c7b0 remove debugging code, resolve some TODOs 2025-06-03 15:54:34 +02:00
Philipp Crocoll
0cd9df7415 fix issues with background sync and multiple databases (especially autoopen) 2025-06-03 15:37:21 +02:00
Philipp Crocoll
a4a3112dc6 fix issues with background operations implementation, added more logging 2025-06-03 14:44:01 +02:00
Philipp Crocoll
6351e1f3d0 towards unifying Background and Blocking Operation Runners. Not working yet, does it run more in non-ui thread? 2025-06-03 11:12:37 +02:00
Philipp Crocoll
2959d8cbcc improve intent when clicking the background-operation notification 2025-06-03 10:16:48 +02:00
Philipp Crocoll
c98680347a fix implementation for BackgroundSyncService for blocking operations 2025-05-27 16:29:42 +02:00
Philipp Crocoll
35367bb28b use ActionInContextInstanceOnOperationFinished where appropriate 2025-05-27 15:54:01 +02:00
Philipp Crocoll
8a1890bc10 refactor and improve EntryEditActivity: Ensure that configuration changes are handled properly 2025-05-27 15:26:09 +02:00
Philipp Crocoll
bf4035fcfe refactor EntryEditActivity, extracting views for entry-edit sections 2025-05-27 12:36:37 +02:00
Philipp Crocoll
d3dfbaab4b start using Pending Actions in EntryEditActivity. Extra fields still causing trouble on config change. 2025-05-27 11:59:26 +02:00
Philipp Crocoll
227074efb6 remove superfluous character from layout 2025-05-27 11:58:47 +02:00
Philipp Crocoll
69f79c1b20 change blocking operations to use BackgroundSyncService also. Introduce pending actions for context instances. 2025-05-27 11:57:58 +02:00
Philipp Crocoll
000d1254ec make parameter naming more consistent 2025-05-27 11:54:55 +02:00
Philipp Crocoll
324fc1f2ee allow to cancel background operations manually or when another operation starts; block database updates in certain activities, e.g. EntryEdit; 2025-05-20 17:05:13 +02:00
Philipp Crocoll
52121c6a85 split file into one file per class 2025-05-13 21:46:24 +02:00
Philipp Crocoll
c6f494ac33 remove no longer needed lock 2025-05-13 21:37:45 +02:00
Philipp Crocoll
400e171bc5 implement UI updates after background sync for Group activity and Entry activity 2025-05-13 21:34:06 +02:00
Philipp Crocoll
41e6e67e87 add support for yes/no/cancel question during background sync 2025-05-13 16:42:50 +02:00
Philipp Crocoll
8277283ebc only enable sync-in-background mode when file is already cached 2025-05-13 14:52:13 +02:00
Philipp Crocoll
71806178d0 tasks no longer store references to activities. These can "expire" and are hard to update (e.g. if one task creates another). Instead, the app can provide the currently active context. 2025-05-13 14:51:44 +02:00
Philipp Crocoll
aa2e4b856d introduce BackgroundSyncService to keep app alive while sync is going on. 2025-05-13 12:38:21 +02:00
Philipp Crocoll
cfb185b53d LoadDb and SaveDb respect the SyncInBackground preference 2025-05-13 11:10:26 +02:00
Philipp Crocoll
c3b6612591 let database sync run in the background. not handling all cases yet. 2025-05-13 09:23:57 +02:00
Philipp Crocoll
fefcf8f30e rename SynchronizeDatabase => StartSynchronizeDatabase; add experimental UI for displaying sync-in-progress 2025-05-06 13:29:47 +02:00
Philipp Crocoll
e95cc84a15 rename ProgressTask => BlockingOperationRunner 2025-04-29 15:10:54 +02:00
Philipp Crocoll
c0ed185612 rename RunnableOnFinish => OperationWithFinishHandler, introduce IKp2aStatusLogger 2025-04-29 15:08:09 +02:00
Philipp Crocoll
61c871f782 rename OnFinish => OnOperationFinishedHandler 2025-04-29 13:45:24 +02:00
134 changed files with 6228 additions and 15756 deletions

View File

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

View File

@@ -1,147 +0,0 @@
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/unused.txt
/src/Kp2aBusinessLogic/Io/DropboxFileStorage.g.cs
/src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs
/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)
#
# append the Configuration variable to 'make' call with value to use in '/p:Configuration='
# of dotnetbuild command.
# of msbuild command.
#
# append the Flavor variable to 'make' call with value to use in '/p:Flavor='
# of dotnetbuild command.
# of msbuild command.
#
# Example:
# make Configuration=Release Flavor=NoNet
@@ -18,7 +18,7 @@
# - native: build the native libs
# - java: build the java libs
# - nuget: restore NuGet packages
# - dotnetbuild: build the project
# - msbuild: build the project
# - apk: same as all
# - 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_java: call clean target of java libs
# - clean_nuget: cleanup the 'nuget restore'
# - clean_dotnet: call clean target of dotnetbuild
# - clean_msbuild: call clean target of msbuild
#
#
#
@@ -60,23 +60,45 @@ $(info MAKESHELL: $(MAKESHELL))
$(info SHELL: $(SHELL))
$(info )
# On linux use xabuild, on Windows use MSBuild.exe, otherwise (macos?) use msbuild.
ifeq ($(detected_OS),Linux)
DOTNET_binary := dotnet
DOTNET := $(shell $(WHICH) $(DOTNET_binary))
MSBUILD_binary := xabuild
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary))
else ifeq ($(detected_OS),Windows)
DOTNET_binary := dotnet
DOTNET := $(shell $(WHICH) $(DOTNET_binary) 2> nul)
MSBUILD_binary := MSBuild.exe
MSBUILD := $(shell $(WHICH) $(MSBUILD_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
DOTNET_binary := dotnet
DOTNET := $(shell $(WHICH) $(DOTNET_binary))
MSBUILD_binary := msbuild
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary))
endif
ifeq ($(DOTNET),)
ifeq ($(MSBUILD),)
$(info )
$(info '$(DOTNET_binary)' binary could not be found. Check it is in your PATH.)
$(info '$(MSBUILD_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 )
endif
$(info DOTNET: $(DOTNET))
$(info MSBUILD: $(MSBUILD))
$(info )
ifeq ($(ANDROID_SDK_ROOT),)
@@ -95,7 +117,7 @@ endif
$(info ANDROID_NDK_ROOT: $(ANDROID_NDK_ROOT))
ifneq ($(Configuration),)
DOTNET_PARAM = -p:Configuration="$(Configuration)"
MSBUILD_PARAM = -p:Configuration="$(Configuration)"
else
$(warning Configuration environment variable not set.)
endif
@@ -105,7 +127,7 @@ CREATE_MANIFEST_LINK :=
MANIFEST_FILE :=
ifneq ($(Flavor),)
DOTNET_PARAM += -p:Flavor="$(Flavor)"
MSBUILD_PARAM += -p:Flavor="$(Flavor)"
ifneq ($(Flavor),)
ifeq ($(Flavor),Debug)
MANIFEST_FILE := AndroidManifest_debug.xml
@@ -130,7 +152,7 @@ else
endif
ifneq ($(KeyStore),)
DOTNET_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a"
MSBUILD_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a"
endif
ifeq ($(detected_OS),Windows)
@@ -154,7 +176,7 @@ endif
# Recursive wildcard: https://stackoverflow.com/a/18258352
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
$(info DOTNET_PARAM: $(DOTNET_PARAM))
$(info MSBUILD_PARAM: $(MSBUILD_PARAM))
$(info nuget path: $(shell $(WHICH) nuget))
$(info )
@@ -232,7 +254,7 @@ OUTPUT_PluginQR = src/java/Keepass2AndroidPluginSDK2/app/build/outputs/aar/Keepa
.PHONY: native $(NATIVE_COMPONENTS) clean_native $(NATIVE_CLEAN_TARGETS) \
java $(JAVA_COMPONENTS) clean_java $(JAVA_CLEAN_TARGETS) \
nuget clean_nuget \
dotnetbuild clean_dotnet \
msbuild clean_msbuild \
apk all clean
all: apk
@@ -281,7 +303,7 @@ ifeq ($(shell $(WHICH) nuget),)
endif
$(RMFILE) stamp.nuget_*
nuget restore src/KeePass.sln
$(DOTNET) restore src/KeePass.sln $(DOTNET_PARAM) -p:RestorePackagesConfig=true
$(MSBUILD) src/KeePass.sln -t:restore $(MSBUILD_PARAM) -p:RestorePackagesConfig=true
@echo "" > stamp.nuget_$(Flavor)
manifestlink:
@@ -290,21 +312,20 @@ manifestlink:
$(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
dotnetbuild: manifestlink native java nuget
$(DOTNET) build src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(DOTNET_PARAM) -p:Platform="Any CPU" -m
msbuild: manifestlink native java nuget src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs
$(MSBUILD) src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(MSBUILD_PARAM) -p:Platform="Any CPU" -m
apk: 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
apk: msbuild
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m
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
build_all: msbuild
##### Cleanup targets
@@ -348,10 +369,10 @@ else
endif
$(RMFILE) stamp.nuget_*
clean_dotnet:
$(DOTNET) clean src/KeePass.sln $(DOTNET_PARAM)
clean_msbuild:
$(MSBUILD) src/KeePass.sln -target:clean $(MSBUILD_PARAM)
clean: clean_native clean_java clean_nuget clean_dotnet
clean: clean_native clean_java clean_nuget clean_msbuild
distclean: 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).
# How can I contribute?
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](https://crowdin.net/project/keepass2android)
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://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.
* [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC)
* [Make a donation](https://philipp.crocoll.net/donate.php)
* [Make a donation](http://philipp.crocoll.net/donate.php)
# How do I learn more?
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
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
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
@@ -40,8 +40,8 @@ namespace KeePassLib.Cryptography
Null = 0,
/// <summary>
/// A variant of the ArcFour algorithm (RC4 incompatible).
/// Insecure; for backward compatibility only.
/// A variant of the ARCFour algorithm (RC4 incompatible).
/// </summary>
/// </summary>
ArcFourVariant = 1,
@@ -66,60 +66,56 @@ namespace KeePassLib.Cryptography
/// </summary>
public sealed class CryptoRandomStream : IDisposable
{
private readonly CrsAlgorithm m_alg;
private bool m_bDisposed = false;
private readonly CrsAlgorithm m_crsAlgorithm;
private readonly byte[] m_pbKey = null;
private readonly byte[] m_pbIV = null;
private readonly ChaCha20Cipher m_chacha20 = null;
private readonly Salsa20Cipher m_salsa20 = null;
private readonly byte[] m_pbState = null;
private byte[] m_pbState = null;
private byte m_i = 0;
private byte m_j = 0;
private Salsa20Cipher m_salsa20 = null;
private ChaCha20Cipher m_chacha20 = null;
/// <summary>
/// Construct a new cryptographically secure random stream object.
/// </summary>
/// <param name="a">Algorithm to use.</param>
/// <param name="pbKey">Initialization key. Must not be <c>null</c>
/// and must contain at least 1 byte.</param>
/// <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");
}
m_alg = a;
/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception>
m_crsAlgorithm = a;
/// <exception cref="System.ArgumentException">Thrown if the
if(a == CrsAlgorithm.ChaCha20)
{
m_pbKey = new byte[32];
m_pbIV = new byte[12];
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, m_pbKey, 32);
Array.Copy(pbHash, 32, m_pbIV, 0, 12);
Array.Copy(pbHash, pbKey32, 32);
Array.Copy(pbHash, 32, pbIV12, 0, 12);
MemUtil.ZeroByteArray(pbHash);
}
m_chacha20 = new ChaCha20Cipher(m_pbKey, m_pbIV, true);
/// algorithm is unknown.</exception>
m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true);
}
else if(a == CrsAlgorithm.Salsa20)
{
m_pbKey = CryptoUtil.HashSha256(pbKey);
m_pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
byte[] pbKey32 = CryptoUtil.HashSha256(pbKey);
byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
0x97, 0x20, 0x5D, 0x2A }; // Unique constant
m_salsa20 = new Salsa20Cipher(m_pbKey, m_pbIV);
m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8);
}
else if(a == CrsAlgorithm.ArcFourVariant)
{
@@ -163,22 +159,17 @@ namespace KeePassLib.Cryptography
{
if(disposing)
{
if (m_alg == CrsAlgorithm.ChaCha20)
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20)
m_chacha20.Dispose();
else if (m_alg == CrsAlgorithm.Salsa20)
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20)
m_salsa20.Dispose();
else if (m_alg == CrsAlgorithm.ArcFourVariant)
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant)
{
MemUtil.ZeroByteArray(m_pbState);
m_i = 0;
m_j = 0;
}
else { Debug.Assert(false); }
if (m_pbKey != null) MemUtil.ZeroByteArray(m_pbKey);
if (m_pbIV != null) MemUtil.ZeroByteArray(m_pbIV);
m_bDisposed = true;
}
}
@@ -189,20 +180,19 @@ namespace KeePassLib.Cryptography
/// <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)
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20)
m_chacha20.Encrypt(pbRet, 0, cb);
else if (m_alg == CrsAlgorithm.Salsa20)
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20)
m_salsa20.Encrypt(pbRet, 0, cb);
else if (m_alg == CrsAlgorithm.ArcFourVariant)
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant)
{
unchecked
{
@@ -231,25 +221,6 @@ namespace KeePassLib.Cryptography
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
public static string Benchmark()
{
@@ -266,20 +237,21 @@ namespace KeePassLib.Cryptography
return str;
}
private static int BenchTime(CrsAlgorithm a, int nRounds, int cbData)
private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize)
{
byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 };
int tStart = Environment.TickCount;
int nStart = Environment.TickCount;
for(int i = 0; i < nRounds; ++i)
{
using(CryptoRandomStream crs = new CryptoRandomStream(a, pbKey))
using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey))
{
crs.GetRandomBytes((uint)cbData);
c.GetRandomBytes((uint)nDataSize);
}
}
int nEnd = Environment.TickCount;
return (Environment.TickCount - tStart);
return (nEnd - nStart);
}
#endif
}

View File

@@ -0,0 +1,65 @@
/*
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

@@ -0,0 +1,173 @@
/*
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
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
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
@@ -19,81 +19,122 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using KeePassLib.Utility;
using System.Diagnostics;
namespace KeePassLib.Cryptography.PasswordGenerator
{
public sealed class PwCharSet : IEquatable<PwCharSet>
public sealed class PwCharSet
{
public static readonly string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static readonly string LowerCase = "abcdefghijklmnopqrstuvwxyz";
public static readonly string Digits = "0123456789";
public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string LowerCase = "abcdefghijklmnopqrstuvwxyz";
public const string Digits = "0123456789";
public static readonly string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ";
public static readonly string LowerConsonants = "bcdfghjklmnpqrstvwxyz";
public static readonly string UpperVowels = "AEIOU";
public static readonly string LowerVowels = "aeiou";
public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ";
public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz";
public const string UpperVowels = "AEIOU";
public const string LowerVowels = "aeiou";
public static readonly string Punctuation = ",.;:";
public static readonly string Brackets = @"[]{}()<>";
public const string Punctuation = @",.;:";
public const string Brackets = @"[]{}()<>";
public static readonly string Special = "!\"#$%&'*+,./:;=?@\\^`|~";
public static readonly string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
public static readonly string UpperHex = "0123456789ABCDEF";
public static readonly string LowerHex = "0123456789abcdef";
public const string UpperHex = "0123456789ABCDEF";
public const string LowerHex = "0123456789abcdef";
public static readonly string LookAlike = "O0Il1|";
public const string Invalid = "\t\r\n";
public const string LookAlike = @"O0l1I|";
internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
private const int CharTabSize = (0x10000 / 8);
private List<char> m_vChars = new List<char>();
private byte[] m_vTab = new byte[CharTabSize];
private static string m_strHighAnsi = null;
public static string HighAnsiChars
{
get
{
if(m_strHighAnsi == null) { new PwCharSet(); } // Create string
Debug.Assert(m_strHighAnsi != null);
return m_strHighAnsi;
}
}
private static string m_strSpecial = null;
public static string SpecialChars
{
get
{
if(m_strSpecial == null) { new PwCharSet(); } // Create string
Debug.Assert(m_strSpecial != null);
return m_strSpecial;
}
}
/// <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";
// internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
[Obsolete]
public static string SpecialChars { get { return PwCharSet.Special; } }
[Obsolete]
public static string HighAnsiChars { get { return PwCharSet.Latin1S; } }
private readonly List<char> m_lChars = new List<char>();
private readonly byte[] m_vTab = new byte[0x10000 / 8];
/// <summary>
/// Create a new, empty character set.
/// Create a new, empty character set collection object.
/// </summary>
public PwCharSet()
{
Debug.Assert(PwCharSet.Latin1S.Length == (16 * 6 - 2));
Initialize(true);
}
public PwCharSet(string strCharSet)
{
Initialize(true);
Add(strCharSet);
}
private PwCharSet(bool bFullInitialize)
{
Initialize(bFullInitialize);
}
private void Initialize(bool bFullInitialize)
{
Clear();
if(!bFullInitialize) return;
if(m_strHighAnsi == null)
{
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();
}
if(m_strSpecial == null)
{
PwCharSet pcs = new PwCharSet(false);
pcs.AddRange('!', '/');
pcs.AddRange(':', '@');
pcs.AddRange('[', '`');
pcs.Add(@"|~");
pcs.Remove(@"-_ ");
pcs.Remove(PwCharSet.Brackets);
m_strSpecial = pcs.ToString();
}
}
/// <summary>
/// Number of characters in this set.
/// </summary>
public uint Size
{
get { return (uint)m_lChars.Count; }
get { return (uint)m_vChars.Count; }
}
/// <summary>
@@ -106,39 +147,19 @@ namespace KeePassLib.Cryptography.PasswordGenerator
{
get
{
if (uPos >= (uint)m_lChars.Count)
if(uPos >= (uint)m_vChars.Count)
throw new ArgumentOutOfRangeException("uPos");
return m_lChars[(int)uPos];
return m_vChars[(int)uPos];
}
}
public bool Equals(PwCharSet other)
{
if (object.ReferenceEquals(other, this)) return true;
if (object.ReferenceEquals(other, null)) return false;
if (m_lChars.Count != other.m_lChars.Count) return false;
return MemUtil.ArraysEqual(m_vTab, other.m_vTab);
}
public override bool Equals(object obj)
{
return Equals(obj as PwCharSet);
}
public override int GetHashCode()
{
return (int)MemUtil.Hash32(m_vTab, 0, m_vTab.Length);
}
/// <summary>
/// Remove all characters from this set.
/// </summary>
public void Clear()
{
m_lChars.Clear();
m_vChars.Clear();
Array.Clear(m_vTab, 0, m_vTab.Length);
}
@@ -170,7 +191,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator
if(!Contains(ch))
{
m_lChars.Add(ch);
m_vChars.Add(ch);
m_vTab[ch / 8] |= (byte)(1 << (ch % 8));
}
}
@@ -184,6 +205,8 @@ namespace KeePassLib.Cryptography.PasswordGenerator
Debug.Assert(strCharSet != null);
if(strCharSet == null) throw new ArgumentNullException("strCharSet");
m_vChars.Capacity = m_vChars.Count + strCharSet.Length;
foreach(char ch in strCharSet)
Add(ch);
}
@@ -203,6 +226,8 @@ namespace KeePassLib.Cryptography.PasswordGenerator
public void AddRange(char chMin, char chMax)
{
m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1;
for(char ch = chMin; ch < chMax; ++ch)
Add(ch);
@@ -216,13 +241,11 @@ namespace KeePassLib.Cryptography.PasswordGenerator
switch(chCharSetIdentifier)
{
case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
case 'A':
Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
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,
case 'C': Add(PwCharSet.LowerConsonants,
PwCharSet.UpperConsonants); break;
case 'z': Add(PwCharSet.UpperConsonants); break;
case 'd': Add(PwCharSet.Digits); break; // Digit
@@ -234,13 +257,12 @@ namespace KeePassLib.Cryptography.PasswordGenerator
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);
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;
case 'x': Add(m_strHighAnsi); break;
default: bResult = false; break;
}
@@ -250,7 +272,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator
public bool Remove(char ch)
{
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
return m_lChars.Remove(ch);
return m_vChars.Remove(ch);
}
public bool Remove(string strCharacters)
@@ -284,8 +306,8 @@ namespace KeePassLib.Cryptography.PasswordGenerator
/// <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)
StringBuilder sb = new StringBuilder();
foreach(char ch in m_vChars)
sb.Append(ch);
return sb.ToString();
@@ -298,13 +320,13 @@ namespace KeePassLib.Cryptography.PasswordGenerator
sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Special) ? 'S' : '_');
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(@"-") ? 'm' : '_');
sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_');
sb.Append(RemoveIfAllExist(@" ") ? 's' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Latin1S) ? 'H' : '_');
sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_');
return sb.ToString();
}
@@ -317,13 +339,13 @@ namespace KeePassLib.Cryptography.PasswordGenerator
if(strRanges[0] != '_') Add(PwCharSet.UpperCase);
if(strRanges[1] != '_') Add(PwCharSet.LowerCase);
if(strRanges[2] != '_') Add(PwCharSet.Digits);
if (strRanges[3] != '_') Add(PwCharSet.Special);
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(PwCharSet.Latin1S);
if(strRanges[9] != '_') Add(m_strHighAnsi);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2016 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
@@ -20,13 +20,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
using KeePassLib.Resources;
using KeePassLib.Security;
using KeePassLib.Utility;
@@ -37,78 +33,95 @@ namespace KeePassLib.Cryptography.PasswordGenerator
Success = 0,
Unknown = 1,
TooFewCharacters = 2,
UnknownAlgorithm = 3,
InvalidCharSet = 4,
InvalidPattern = 5
UnknownAlgorithm = 3
}
/// <summary>
/// Password generator.
/// Utility functions for generating random passwords.
/// </summary>
public static class PwGenerator
{
private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy,
out byte[] pbKey)
public static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, byte[] pbUserEntropy,
CustomPwGeneratorPool pwAlgorithmPool)
{
pbKey = CryptoRandom.Instance.GetRandomBytes(128);
Debug.Assert(pwProfile != null);
if (pwProfile == null) throw new ArgumentNullException("pwProfile");
CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy);
PwgError e = PwgError.Unknown;
if (pwProfile.GeneratorType == PasswordGeneratorType.CharSet)
e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs);
else if (pwProfile.GeneratorType == PasswordGeneratorType.Pattern)
e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs);
else if (pwProfile.GeneratorType == PasswordGeneratorType.Custom)
e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool);
else { Debug.Assert(false); psOut = ProtectedString.Empty; }
return e;
}
private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy)
{
byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128);
// Mix in additional entropy
Debug.Assert(pbKey.Length >= 64);
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length != 0))
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0))
{
using (SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
MemUtil.ZeroByteArray(pbHash);
}
}
return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
}
internal static char GenerateCharacter(PwCharSet pwCharSet,
CryptoRandomStream crsRandomSource)
internal static char GenerateCharacter(PwProfile pwProfile,
PwCharSet pwCharSet, CryptoRandomStream crsRandomSource)
{
uint cc = pwCharSet.Size;
if (cc == 0) return char.MinValue;
if (pwCharSet.Size == 0) return char.MinValue;
uint i = (uint)crsRandomSource.GetRandomUInt64(cc);
return pwCharSet[i];
ulong uIndex = crsRandomSource.GetRandomUInt64();
uIndex %= (ulong)pwCharSet.Size;
char ch = pwCharSet[(uint)uIndex];
if (pwProfile.NoRepeatingCharacters)
pwCharSet.Remove(ch);
return ch;
}
internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
internal static void 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;
}
pwCharSet.Remove(PwCharSet.Invalid);
if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
if (!string.IsNullOrEmpty(pwProfile.ExcludeCharacters))
if (pwProfile.ExcludeCharacters.Length > 0)
pwCharSet.Remove(pwProfile.ExcludeCharacters);
return true;
}
internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource)
internal static void ShufflePassword(char[] pPassword,
CryptoRandomStream crsRandomSource)
{
if (v == null) { Debug.Assert(false); return; }
if (crsRandomSource == null) { Debug.Assert(false); return; }
Debug.Assert(pPassword != null); if (pPassword == null) return;
Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return;
for (int i = v.Length - 1; i >= 1; --i)
if (pPassword.Length <= 1) return; // Nothing to shuffle
for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect)
{
int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1));
ulong uRandomIndex = crsRandomSource.GetRandomUInt64();
uRandomIndex %= (ulong)(pPassword.Length - nSelect);
char t = v[i];
v[i] = v[j];
v[j] = t;
char chTemp = pPassword[nSelect];
pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex];
pPassword[nSelect + (int)uRandomIndex] = chTemp;
}
}
@@ -122,7 +135,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator
if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm;
string strID = pwProfile.CustomAlgorithmUuid;
if (string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm;
if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
byte[] pbUuid = Convert.FromBase64String(strID);
PwUuid uuid = new PwUuid(pbUuid);
@@ -135,57 +148,5 @@ namespace KeePassLib.Cryptography.PasswordGenerator
psOut = pwd;
return PwgError.Success;
}
internal static string ErrorToString(PwgError e, bool bHeader)
{
if (e == PwgError.Success) { Debug.Assert(false); return string.Empty; }
if ((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed;
string str = KLRes.UnknownError;
switch (e)
{
// case PwgError.Success:
// break;
case PwgError.Unknown:
break;
case PwgError.TooFewCharacters:
str = KLRes.CharSetTooFewChars;
break;
case PwgError.UnknownAlgorithm:
str = KLRes.AlgorithmUnknown;
break;
case PwgError.InvalidCharSet:
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
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
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
@@ -19,8 +19,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Diagnostics;
using KeePassLib.Utility;
@@ -28,17 +28,15 @@ namespace KeePassLib.Cryptography
{
public static class PopularPasswords
{
private static readonly Dictionary<int, Dictionary<char[], bool>> g_dicts =
new Dictionary<int, Dictionary<char[], bool>>();
private static Dictionary<int, Dictionary<string, bool>> m_dicts =
new Dictionary<int, Dictionary<string, bool>>();
internal static int MaxLength
{
get
{
Debug.Assert(g_dicts.Count > 0); // Should be initialized
int iMaxLen = 0;
foreach (int iLen in g_dicts.Keys)
foreach(int iLen in m_dicts.Keys)
{
if(iLen > iMaxLen) iMaxLen = iLen;
}
@@ -49,8 +47,8 @@ namespace KeePassLib.Cryptography
internal static bool ContainsLength(int nLength)
{
Dictionary<char[], bool> dDummy;
return g_dicts.TryGetValue(nLength, out dDummy);
Dictionary<string, bool> dDummy;
return m_dicts.TryGetValue(nLength, out dDummy);
}
public static bool IsPopularPassword(char[] vPassword)
@@ -64,30 +62,28 @@ namespace KeePassLib.Cryptography
if(vPassword == null) throw new ArgumentNullException("vPassword");
if(vPassword.Length == 0) { uDictSize = 0; return false; }
#if DEBUG
Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch)));
#endif
string str = new string(vPassword);
try { return IsPopularPasswordPriv(vPassword, out uDictSize); }
try { return IsPopularPasswordPriv(str, out uDictSize); }
catch(Exception) { Debug.Assert(false); }
uDictSize = 0;
return false;
}
private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize)
private static bool IsPopularPasswordPriv(string str, out ulong uDictSize)
{
Debug.Assert(g_dicts.Count > 0); // Should be initialized with data
Debug.Assert(m_dicts.Count > 0); // Should be initialized with data
Dictionary<char[], bool> d;
if (!g_dicts.TryGetValue(vPassword.Length, out d))
Dictionary<string, bool> d;
if(!m_dicts.TryGetValue(str.Length, out d))
{
uDictSize = 0;
return false;
}
uDictSize = (ulong)d.Count;
return d.ContainsKey(vPassword);
return d.ContainsKey(str);
}
public static void Add(byte[] pbData, bool bGZipped)
@@ -100,27 +96,30 @@ namespace KeePassLib.Cryptography
string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length);
if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; }
if(!char.IsWhiteSpace(strData[strData.Length - 1]))
strData += "\n";
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= strData.Length; ++i)
for(int i = 0; i < strData.Length; ++i)
{
char ch = ((i == strData.Length) ? ' ' : strData[i]);
char ch = strData[i];
if(char.IsWhiteSpace(ch))
{
int cc = sb.Length;
if(cc > 0)
{
char[] vWord = new char[cc];
sb.CopyTo(0, vWord, 0, cc);
string strWord = sb.ToString();
Debug.Assert(strWord.Length == cc);
Dictionary<char[], bool> d;
if (!g_dicts.TryGetValue(cc, out d))
Dictionary<string, bool> d;
if(!m_dicts.TryGetValue(cc, out d))
{
d = new Dictionary<char[], bool>(MemUtil.ArrayHelperExOfChar);
g_dicts[cc] = d;
d = new Dictionary<string, bool>();
m_dicts[cc] = d;
}
d[vWord] = true;
d[strWord] = true;
sb.Remove(0, cc);
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
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
@@ -19,8 +19,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Diagnostics;
using KeePassLib.Cryptography.PasswordGenerator;
using KeePassLib.Utility;
@@ -35,19 +35,19 @@ namespace KeePassLib.Cryptography
{
private static class PatternID
{
internal const char LowerAlpha = 'L';
internal const char UpperAlpha = 'U';
internal const char Digit = 'D';
internal const char Special = 'S';
internal const char Latin1S = 'H';
internal const char Other = 'X';
public const char LowerAlpha = 'L';
public const char UpperAlpha = 'U';
public const char Digit = 'D';
public const char Special = 'S';
public const char High = 'H';
public const char Other = 'X';
internal const char Dictionary = 'W';
internal const char Repetition = 'R';
internal const char Number = 'N';
internal const char DiffSeq = 'C';
public const char Dictionary = 'W';
public const char Repetition = 'R';
public const char Number = 'N';
public const char DiffSeq = 'C';
internal const string All = "LUDSHXWRNC";
public const string All = "LUDSHXWRNC";
}
// private static class CharDistrib
@@ -125,7 +125,7 @@ namespace KeePassLib.Cryptography
private sealed class EntropyEncoder
{
private readonly string m_strAlph;
private readonly Dictionary<char, ulong> m_dHisto = new Dictionary<char, ulong>();
private Dictionary<char, ulong> m_dHisto = new Dictionary<char, ulong>();
private readonly ulong m_uBaseWeight;
private readonly ulong m_uCharWeight;
private readonly ulong m_uOccExclThreshold;
@@ -189,7 +189,7 @@ namespace KeePassLib.Cryptography
private sealed class MultiEntropyEncoder
{
private readonly Dictionary<char, EntropyEncoder> m_dEncs =
private Dictionary<char, EntropyEncoder> m_dEncs =
new Dictionary<char, EntropyEncoder>();
public MultiEntropyEncoder()
@@ -281,7 +281,7 @@ namespace KeePassLib.Cryptography
}
}
private static readonly object m_objSyncInit = new object();
private static object m_objSyncInit = new object();
private static List<QeCharType> m_lCharTypes = null;
private static void EnsureInitialized()
@@ -292,20 +292,25 @@ namespace KeePassLib.Cryptography
{
string strSpecial = PwCharSet.PrintableAsciiSpecial;
if(strSpecial.IndexOf(' ') >= 0) { Debug.Assert(false); }
else strSpecial += " ";
else strSpecial = strSpecial + " ";
int nSp = strSpecial.Length;
int nL1S = PwCharSet.Latin1S.Length;
int nHi = PwCharSet.HighAnsiChars.Length;
m_lCharTypes = new List<QeCharType>()
{
new QeCharType(PatternID.LowerAlpha, PwCharSet.LowerCase, true),
new QeCharType(PatternID.UpperAlpha, PwCharSet.UpperCase, true),
new QeCharType(PatternID.Digit, PwCharSet.Digits, true),
new QeCharType(PatternID.Special, strSpecial, false),
new QeCharType(PatternID.Latin1S, PwCharSet.Latin1S, false),
new QeCharType(PatternID.Other, 0x10000 - (2 * 26) - 10 - nSp - nL1S)
};
m_lCharTypes = new List<QeCharType>();
m_lCharTypes.Add(new QeCharType(PatternID.LowerAlpha,
PwCharSet.LowerCase, true));
m_lCharTypes.Add(new QeCharType(PatternID.UpperAlpha,
PwCharSet.UpperCase, true));
m_lCharTypes.Add(new QeCharType(PatternID.Digit,
PwCharSet.Digits, true));
m_lCharTypes.Add(new QeCharType(PatternID.Special,
strSpecial, false));
m_lCharTypes.Add(new QeCharType(PatternID.High,
PwCharSet.HighAnsiChars, false));
m_lCharTypes.Add(new QeCharType(PatternID.Other,
0x10000 - (2 * 26) - 10 - nSp - nHi));
}
}
}
@@ -313,30 +318,30 @@ namespace KeePassLib.Cryptography
/// <summary>
/// Estimate the quality of a password.
/// </summary>
/// <param name="vPassword">Password to check.</param>
/// <param name="vPasswordChars">Password to check.</param>
/// <returns>Estimated bit-strength of the password.</returns>
public static uint EstimatePasswordBits(char[] vPassword)
public static uint EstimatePasswordBits(char[] vPasswordChars)
{
if (vPassword == null) { Debug.Assert(false); return 0; }
if (vPassword.Length == 0) return 0;
if(vPasswordChars == null) { Debug.Assert(false); return 0; }
if(vPasswordChars.Length == 0) return 0;
EnsureInitialized();
int n = vPassword.Length;
int n = vPasswordChars.Length;
List<QePatternInstance>[] vPatterns = new List<QePatternInstance>[n];
for(int i = 0; i < n; ++i)
{
vPatterns[i] = new List<QePatternInstance>();
QePatternInstance piChar = new QePatternInstance(i, 1,
GetCharType(vPassword[i]));
GetCharType(vPasswordChars[i]));
vPatterns[i].Add(piChar);
}
FindRepetitions(vPassword, vPatterns);
FindNumbers(vPassword, vPatterns);
FindDiffSeqs(vPassword, vPatterns);
FindPopularPasswords(vPassword, vPatterns);
FindRepetitions(vPasswordChars, vPatterns);
FindNumbers(vPasswordChars, vPatterns);
FindDiffSeqs(vPasswordChars, vPatterns);
FindPopularPasswords(vPasswordChars, vPatterns);
// Encoders must not be static, because the entropy estimation
// may run concurrently in multiple threads and the encoders are
@@ -377,7 +382,7 @@ namespace KeePassLib.Cryptography
{
Debug.Assert(s.Position == n);
double dblCost = ComputePathCost(s.Path, vPassword,
double dblCost = ComputePathCost(s.Path, vPasswordChars,
ecPattern, mcData);
if(dblCost < dblMinCost) dblMinCost = dblCost;
}
@@ -415,12 +420,11 @@ namespace KeePassLib.Cryptography
{
if(pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; }
char[] v = StrUtil.Utf8.GetChars(pbUnprotectedUtf8);
uint r;
try { r = EstimatePasswordBits(v); }
finally { MemUtil.ZeroArray<char>(v); }
char[] vChars = StrUtil.Utf8.GetChars(pbUnprotectedUtf8);
uint uResult = EstimatePasswordBits(vChars);
MemUtil.ZeroArray<char>(vChars);
return r;
return uResult;
}
private static QeCharType GetCharType(char ch)
@@ -478,7 +482,7 @@ namespace KeePassLib.Cryptography
vLeet[i] = char.ToLower(DecodeLeetChar(ch));
}
char chErased = default(char); // The value that Array.Clear uses
char chErased = default(char);
Debug.Assert(chErased == char.MinValue);
int nMaxLen = Math.Min(n, PopularPasswords.MaxLength);
@@ -511,12 +515,7 @@ namespace KeePassLib.Cryptography
Debug.Assert(vLower[i] == chErased);
}
}
MemUtil.ZeroArray<char>(vSub);
}
MemUtil.ZeroArray<char>(vLower);
MemUtil.ZeroArray<char>(vLeet);
}
private static bool EvalAddPopularPasswordPattern(List<QePatternInstance>[] vPatterns,
@@ -660,8 +659,6 @@ namespace KeePassLib.Cryptography
if(bFoundRep) ErasePart(v, x1, m, ref chErased);
}
}
MemUtil.ZeroArray<char>(v);
}
private static bool PartsEqual(char[] v, int x1, int x2, int nLength)
@@ -688,25 +685,23 @@ namespace KeePassLib.Cryptography
{
int n = vPassword.Length;
StringBuilder sb = new StringBuilder();
for(int i = 0; i < n; ++i)
{
char ch = vPassword[i];
if((ch >= '0') && (ch <= '9')) sb.Append(ch);
else
{
AddNumberPattern(vPatterns, sb, i - sb.Length);
AddNumberPattern(vPatterns, sb.ToString(), i - sb.Length);
sb.Remove(0, sb.Length);
}
}
AddNumberPattern(vPatterns, sb, n - sb.Length);
AddNumberPattern(vPatterns, sb.ToString(), n - sb.Length);
}
private static void AddNumberPattern(List<QePatternInstance>[] vPatterns,
StringBuilder sb, int i)
string strNumber, int i)
{
if (sb.Length <= 2) return;
string strNumber = sb.ToString();
if(strNumber.Length <= 2) return;
int nZeros = 0;
for(int j = 0; j < strNumber.Length; ++j)
@@ -738,18 +733,17 @@ namespace KeePassLib.Cryptography
private static void FindDiffSeqs(char[] vPassword,
List<QePatternInstance>[] vPatterns)
{
int n = vPassword.Length;
int d = int.MaxValue, p = 0;
int d = int.MinValue, p = 0;
string str = new string(vPassword) + new string(char.MaxValue, 1);
for (int i = 1; i <= n; ++i)
for(int i = 1; i < str.Length; ++i)
{
int dCur = ((i == n) ? int.MinValue :
((int)vPassword[i] - (int)vPassword[i - 1]));
int dCur = (int)str[i] - (int)str[i - 1];
if(dCur != d)
{
if((i - p) >= 3) // At least 3 chars involved
{
QeCharType ct = GetCharType(vPassword[p]);
QeCharType ct = GetCharType(str[p]);
double dblCost = ct.CharSize + Log2(i - p - 1);
vPatterns[p].Add(new QePatternInstance(p,

View File

@@ -46,12 +46,4 @@ namespace KeePassLib.Delegates
public delegate void VoidDelegate();
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,6 +86,11 @@ namespace KeePassLib.Interfaces
/// the current work.</returns>
bool SetText(string strNewText, LogStatusType lsType);
void UpdateMessage(String message);
void UpdateSubMessage(String submessage);
/// <summary>
/// Check if the user cancelled the current work.
/// </summary>
@@ -100,6 +105,12 @@ namespace KeePassLib.Interfaces
public void EndLogging() { }
public bool SetProgress(uint uPercent) { return true; }
public bool SetText(string strNewText, LogStatusType lsType) { return true; }
public void UpdateMessage(string message){
}
public void UpdateSubMessage(string submessage){
}
public bool ContinueWork() { return true; }
}
}

View File

@@ -21,6 +21,7 @@ using System.IO;
using Android;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Preferences;
using KeePassLib.Serialization;
@@ -35,7 +36,15 @@ namespace keepass2android
public static void Log(string message)
{
if (message != null)
{
message += Thread.CurrentThread.ManagedThreadId != 0
? " (Thread ID: " + Thread.CurrentThread.ManagedThreadId + ")"
: "";
if (Looper.MainLooper == Looper.MyLooper())
message += " (Main Looper)";
Android.Util.Log.Debug("KP2A", message);
}
if (LogToFile)
{
lock (_fileLocker)
@@ -116,23 +125,12 @@ namespace keepass2android
Intent sendIntent = new Intent();
sendIntent.SetAction(Intent.ActionSend);
string logText = File.ReadAllText(LogFilename);
sendIntent.PutExtra(Intent.ExtraText, logText);
sendIntent.PutExtra(Intent.ExtraText, File.ReadAllText(LogFilename));
sendIntent.PutExtra(Intent.ExtraEmail, "crocoapps@gmail.com");
sendIntent.PutExtra(Intent.ExtraSubject, "Keepass2Android log");
sendIntent.SetType("text/plain");
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)
{

View File

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

View File

@@ -83,7 +83,6 @@ namespace KeePassLib.Serialization
if (m_bUsedOnce)
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
m_bUsedOnce = true;
Kp2aLog.Log("Starting to load KDBX file...");
#if KDBX_BENCHMARK
Stopwatch swTime = Stopwatch.StartNew();
@@ -258,7 +257,6 @@ namespace KeePassLib.Serialization
MessageService.ShowInfo("Loading KDBX took " +
swTime.ElapsedMilliseconds.ToString() + " ms.");
#endif
Kp2aLog.Log("Finished loading KDBX file.");
}
private void CommonCleanUpRead(List<Stream> lStreams, HashingStreamEx sHashing)
@@ -490,7 +488,7 @@ namespace KeePassLib.Serialization
ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
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);
if (bProt) MemUtil.ZeroByteArray(pbData);

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
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
@@ -22,7 +22,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
#if KeePassLibSD
@@ -31,26 +30,81 @@ using KeePassLibSD;
using System.IO.Compression;
#endif
using KeePassLib.Delegates;
namespace KeePassLib.Utility
{
/// <summary>
/// Buffer manipulation and conversion routines.
/// Contains static buffer manipulation and string conversion routines.
/// </summary>
public static class MemUtil
{
public static readonly byte[] EmptyByteArray = new byte[0];
internal static readonly ArrayHelperEx<char> ArrayHelperExOfChar =
new ArrayHelperEx<char>();
private const MethodImplOptions MioNoOptimize =
#if KeePassLibSD
MethodImplOptions.NoInlining;
#else
(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining);
#endif
private static readonly uint[] m_vSBox = new uint[256] {
0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230,
0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4,
0x07CE9E5B, 0x31788A0C, 0xF683F6F4, 0xEA061F49,
0xFA5C2ACA, 0x4B9E494E, 0xB0AB25BA, 0x767731FC,
0x261893A7, 0x2B09F2CE, 0x046261E4, 0x41367B4B,
0x18A7F225, 0x8F923C0E, 0x5EF3A325, 0x28D0435E,
0x84C22919, 0xED66873C, 0x8CEDE444, 0x7FC47C24,
0xFCFC6BA3, 0x676F928D, 0xB4147187, 0xD8FB126E,
0x7D798D17, 0xFF82E424, 0x1712FA5B, 0xABB09DD5,
0x8156BA63, 0x84E4D969, 0xC937FB9A, 0x2F1E5BFC,
0x178ECA11, 0x0E71CD5F, 0x52AAC6F4, 0x71EEFC8F,
0x7090D749, 0x21CACA31, 0x92996378, 0x0939A8A8,
0xE9EE1934, 0xD2718616, 0xF2500543, 0xB911873C,
0xD3CB3EEC, 0x2BA0DBEB, 0xB42D0A27, 0xECE67C0F,
0x302925F0, 0x6114F839, 0xD39E6307, 0xE28970D6,
0xEB982F99, 0x941B4CDF, 0xC540E550, 0x8124FC45,
0x98B025C7, 0xE2BF90EA, 0x4F57C976, 0xCF546FE4,
0x59566DC8, 0xE3F4360D, 0xF5F9D231, 0xD6180B22,
0xB54E088A, 0xB5DFE6A6, 0x3637A36F, 0x056E9284,
0xAFF8FBC5, 0x19E01648, 0x8611F043, 0xDAE44337,
0xF61B6A1C, 0x257ACD9E, 0xDD35F507, 0xEF05CAFA,
0x05EB4A83, 0xFC25CA92, 0x0A4728E6, 0x9CF150EF,
0xAEEF67DE, 0xA9472337, 0x57C81EFE, 0x3E5E009F,
0x02CB03BB, 0x2BA85674, 0xF21DC251, 0x78C34A34,
0xABB1F5BF, 0xB95A2FBD, 0x1FB47777, 0x9A96E8AC,
0x5D2D2838, 0x55AAC92A, 0x99EE324E, 0x10F6214B,
0x58ABDFB1, 0x2008794D, 0xBEC880F0, 0xE75E5341,
0x88015C34, 0x352D8FBF, 0x622B7F6C, 0xF5C59EA2,
0x1F759D8E, 0xADE56159, 0xCC7B4C25, 0x5B8BC48C,
0xB6BD15AF, 0x3C5B5110, 0xE74A7C3D, 0xEE613161,
0x156A1C67, 0x72C06817, 0xEA0A6F69, 0x4CECF993,
0xCA9D554C, 0x8E20361F, 0x42D396B9, 0x595DE578,
0x749D7955, 0xFD1BA5FD, 0x81FC160E, 0xDB97E28C,
0x7CF148F7, 0x0B0B3CF5, 0x534DE605, 0x46421066,
0xD4B68DD1, 0x9E479CE6, 0xAE667A9D, 0xBC082082,
0xB06DD6EF, 0x20F0F23F, 0xB99E1551, 0xF47A2E3A,
0x71DA50C6, 0x67B65779, 0x2A8CB376, 0x1EA71EEE,
0x29ABCD50, 0xB6EB0C6B, 0x23C10511, 0x6F3F2144,
0x6AF23012, 0xF696BD9E, 0xB94099D8, 0xAD5A9C81,
0x7A0794FA, 0x7EDF59D6, 0x1E72E574, 0x8561913C,
0x4E4D568F, 0xEECB9928, 0x9C124D2E, 0x0848B82C,
0xF1CA395F, 0x9DAF43DC, 0xF77EC323, 0x394E9B59,
0x7E200946, 0x8B811D68, 0x16DA3305, 0xAB8DE2C3,
0xE6C53B64, 0x98C2D321, 0x88A97D81, 0xA7106419,
0x8E52F7BF, 0x8ED262AF, 0x7CCA974E, 0xF0933241,
0x040DD437, 0xE143B3D4, 0x3019F56F, 0xB741521D,
0xF1745362, 0x4C435F9F, 0xB4214D0D, 0x0B0C348B,
0x5051D189, 0x4C30447E, 0x7393D722, 0x95CEDD0B,
0xDD994E80, 0xC3D22ED9, 0x739CD900, 0x131EB9C4,
0xEF1062B2, 0x4F0DE436, 0x52920073, 0x9A7F3D80,
0x896E7B1B, 0x2C8BBE5A, 0xBD304F8A, 0xA993E22C,
0x134C41A0, 0xFA989E00, 0x39CE9726, 0xFB89FCCF,
0xE8FBAC97, 0xD4063FFC, 0x935A2B5A, 0x44C8EE83,
0xCB2BC7B6, 0x02989E92, 0x75478BEA, 0x144378D0,
0xD853C087, 0x8897A34E, 0xDD23629D, 0xBDE2A2A2,
0x581D8ECC, 0x5DA8AEE8, 0xFF8AAFD0, 0xBA2BCF6E,
0x4BD98DAC, 0xF2EDB9E4, 0xFA2DC868, 0x47E84661,
0xECEB1C7D, 0x41705CA4, 0x5982E4D4, 0xEB5204A1,
0xD196CAFB, 0x6414804D, 0x3ABD4B46, 0x8B494C26,
0xB432D52B, 0x39C5356B, 0x6EC80BF7, 0x71BE5483,
0xCEC4A509, 0xE9411D61, 0x52F341E5, 0xD2E6197B,
0x4F02826C, 0xA9E48838, 0xD1F8F247, 0xE4957FB3,
0x586CCA99, 0x9A8B6A5B, 0x4998FBEA, 0xF762BE4C,
0x90DFE33C, 0x9731511E, 0x88C6A82F, 0xDD65A4D4
};
/// <summary>
/// Convert a hexadecimal string to a byte array. The input string must be
@@ -89,11 +143,11 @@ namespace KeePassLib.Utility
ch = strHex[i + 1];
if((ch >= '0') && (ch <= '9'))
bt |= (byte)(ch - '0');
bt += (byte)(ch - '0');
else if((ch >= 'a') && (ch <= 'f'))
bt |= (byte)(ch - 'a' + 10);
bt += (byte)(ch - 'a' + 10);
else if((ch >= 'A') && (ch <= 'F'))
bt |= (byte)(ch - 'A' + 10);
bt += (byte)(ch - 'A' + 10);
else { Debug.Assert(false); }
pb[i >> 1] = bt;
@@ -192,26 +246,20 @@ namespace KeePassLib.Utility
return l.ToArray();
}
internal static byte[] ParseBase32(string str, bool bAutoPad)
{
if (str == null) { Debug.Assert(false); return null; }
// https://sourceforge.net/p/keepass/discussion/329220/thread/59b61fddea/
if (bAutoPad && ((str.Length % 8) != 0))
str = str.PadRight((str.Length & ~7) + 8, '=');
return ParseBase32(str);
}
/// <summary>
/// Set all bytes in a byte array to zero.
/// </summary>
/// <param name="pbArray">Input array. All bytes of this array
/// will be set to zero.</param>
[MethodImpl(MioNoOptimize)]
#if KeePassLibSD
[MethodImpl(MethodImplOptions.NoInlining)]
#else
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
#endif
public static void ZeroByteArray(byte[] pbArray)
{
if (pbArray == null) { Debug.Assert(false); return; }
Debug.Assert(pbArray != null);
if(pbArray == null) throw new ArgumentNullException("pbArray");
Array.Clear(pbArray, 0, pbArray.Length);
}
@@ -220,41 +268,18 @@ namespace KeePassLib.Utility
/// Set all elements of an array to the default value.
/// </summary>
/// <param name="v">Input array.</param>
[MethodImpl(MioNoOptimize)]
#if KeePassLibSD
[MethodImpl(MethodImplOptions.NoInlining)]
#else
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
#endif
public static void ZeroArray<T>(T[] v)
{
if (v == null) { Debug.Assert(false); return; }
if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); }
Array.Clear(v, 0, v.Length);
}
private static byte[] g_pbZero = null;
[MethodImpl(MioNoOptimize)]
public static void ZeroMemory(IntPtr pb, long cb)
{
if (pb == IntPtr.Zero) { Debug.Assert(false); return; }
if (cb < 0) { Debug.Assert(false); return; }
byte[] pbZero = g_pbZero;
if (pbZero == null)
{
pbZero = new byte[4096];
g_pbZero = pbZero;
}
long cbZero = pbZero.Length;
while (cb != 0)
{
long cbBlock = Math.Min(cb, cbZero);
Marshal.Copy(pbZero, 0, pb, (int)cbBlock);
pb = AddPtr(pb, cbBlock);
cb -= cbBlock;
}
}
/// <summary>
/// Convert 2 bytes to a 16-bit unsigned integer (little-endian).
/// </summary>
@@ -371,7 +396,15 @@ namespace KeePassLib.Utility
/// </summary>
public static byte[] UInt16ToBytes(ushort uValue)
{
return new byte[2] { (byte)uValue, (byte)(uValue >> 8) };
byte[] pb = new byte[2];
unchecked
{
pb[0] = (byte)uValue;
pb[1] = (byte)(uValue >> 8);
}
return pb;
}
/// <summary>
@@ -379,8 +412,17 @@ namespace KeePassLib.Utility
/// </summary>
public static byte[] UInt32ToBytes(uint uValue)
{
return new byte[4] { (byte)uValue, (byte)(uValue >> 8),
(byte)(uValue >> 16), (byte)(uValue >> 24) };
byte[] pb = new byte[4];
unchecked
{
pb[0] = (byte)uValue;
pb[1] = (byte)(uValue >> 8);
pb[2] = (byte)(uValue >> 16);
pb[3] = (byte)(uValue >> 24);
}
return pb;
}
/// <summary>
@@ -389,27 +431,41 @@ namespace KeePassLib.Utility
public static void UInt32ToBytesEx(uint uValue, byte[] pb, int iOffset)
{
if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); }
if ((iOffset < 0) || (iOffset >= (pb.Length - 3)))
if((iOffset < 0) || ((iOffset + 3) >= pb.Length))
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("iOffset");
}
unchecked
{
pb[iOffset] = (byte)uValue;
pb[iOffset + 1] = (byte)(uValue >> 8);
pb[iOffset + 2] = (byte)(uValue >> 16);
pb[iOffset + 3] = (byte)(uValue >> 24);
}
}
/// <summary>
/// Convert a 64-bit unsigned integer to 8 bytes (little-endian).
/// </summary>
public static byte[] UInt64ToBytes(ulong uValue)
{
return new byte[8] { (byte)uValue, (byte)(uValue >> 8),
(byte)(uValue >> 16), (byte)(uValue >> 24),
(byte)(uValue >> 32), (byte)(uValue >> 40),
(byte)(uValue >> 48), (byte)(uValue >> 56) };
byte[] pb = new byte[8];
unchecked
{
pb[0] = (byte)uValue;
pb[1] = (byte)(uValue >> 8);
pb[2] = (byte)(uValue >> 16);
pb[3] = (byte)(uValue >> 24);
pb[4] = (byte)(uValue >> 32);
pb[5] = (byte)(uValue >> 40);
pb[6] = (byte)(uValue >> 48);
pb[7] = (byte)(uValue >> 56);
}
return pb;
}
/// <summary>
@@ -418,12 +474,14 @@ namespace KeePassLib.Utility
public static void UInt64ToBytesEx(ulong uValue, byte[] pb, int iOffset)
{
if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); }
if ((iOffset < 0) || (iOffset >= (pb.Length - 7)))
if((iOffset < 0) || ((iOffset + 7) >= pb.Length))
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("iOffset");
}
unchecked
{
pb[iOffset] = (byte)uValue;
pb[iOffset + 1] = (byte)(uValue >> 8);
pb[iOffset + 2] = (byte)(uValue >> 16);
@@ -433,27 +491,18 @@ namespace KeePassLib.Utility
pb[iOffset + 6] = (byte)(uValue >> 48);
pb[iOffset + 7] = (byte)(uValue >> 56);
}
}
public static byte[] Int32ToBytes(int iValue)
{
return UInt32ToBytes((uint)iValue);
}
public static void Int32ToBytesEx(int iValue, byte[] pb, int iOffset)
{
UInt32ToBytesEx((uint)iValue, pb, iOffset);
}
public static byte[] Int64ToBytes(long lValue)
{
return UInt64ToBytes((ulong)lValue);
}
public static void Int64ToBytesEx(long lValue, byte[] pb, int iOffset)
{
UInt64ToBytesEx((ulong)lValue, pb, iOffset);
}
public static uint RotateLeft32(uint u, int nBits)
{
return ((u << nBits) | (u >> (32 - nBits)));
@@ -474,35 +523,14 @@ namespace KeePassLib.Utility
return ((u >> nBits) | (u << (64 - nBits)));
}
private static void AddVersionComponent(ref ulong uVersion, int iValue)
{
if (iValue < 0) iValue = 0;
else if (iValue > 0xFFFF) { Debug.Assert(false); iValue = 0xFFFF; }
uVersion = (uVersion << 16) | (uint)iValue;
}
internal static ulong VersionToUInt64(Version v)
{
if (v == null) { Debug.Assert(false); return 0; }
ulong u = 0;
AddVersionComponent(ref u, v.Major);
AddVersionComponent(ref u, v.Minor);
AddVersionComponent(ref u, v.Build);
AddVersionComponent(ref u, v.Revision);
return u;
}
public static bool ArraysEqual(byte[] x, byte[] y)
{
// Return false if one of them is null (not comparable)!
if((x == null) || (y == null)) { Debug.Assert(false); return false; }
int cb = x.Length;
if (cb != y.Length) return false;
if(x.Length != y.Length) return false;
for (int i = 0; i < cb; ++i)
for(int i = 0; i < x.Length; ++i)
{
if(x[i] != y[i]) return false;
}
@@ -528,90 +556,28 @@ namespace KeePassLib.Utility
}
/// <summary>
/// Fast 32-bit hash (e.g. for hash tables).
/// Fast hash that can be used e.g. for hash tables.
/// The algorithm might change in the future; do not store
/// the hashes for later use.
/// </summary>
public static uint Hash32(byte[] pb, int iOffset, int cb)
public static uint Hash32(byte[] v, int iStart, int iLength)
{
const ulong hI = 0x4295DC458269ED9DUL;
const uint hI32 = (uint)(hI >> 32);
uint u = 0x326F637B;
if (pb == null) { Debug.Assert(false); return hI32; }
if (iOffset < 0) { Debug.Assert(false); return hI32; }
if (cb < 0) { Debug.Assert(false); return hI32; }
if(v == null) { Debug.Assert(false); return u; }
if(iStart < 0) { Debug.Assert(false); return u; }
if(iLength < 0) { Debug.Assert(false); return u; }
int m = iOffset + cb;
if ((m < 0) || (m > pb.Length)) { Debug.Assert(false); return hI32; }
int m = iStart + iLength;
if(m > v.Length) { Debug.Assert(false); return u; }
int m4 = iOffset + (cb & ~3), cbR = cb & 3;
ulong h = hI;
for (int i = iOffset; i < m4; i += 4)
h = (pb[i] ^ ((ulong)pb[i + 1] << 8) ^ ((ulong)pb[i + 2] << 16) ^
((ulong)pb[i + 3] << 24) ^ h) * 0x5EA4A1E35C8ACDA3UL;
switch (cbR)
for(int i = iStart; i < m; ++i)
{
case 1:
Debug.Assert(m4 == (m - 1));
h = (pb[m4] ^ h) * 0x54A1CC5970AF27BBUL;
break;
case 2:
Debug.Assert(m4 == (m - 2));
h = (pb[m4] ^ ((ulong)pb[m4 + 1] << 8) ^ h) *
0x6C45CB2537A4271DUL;
break;
case 3:
Debug.Assert(m4 == (m - 3));
h = (pb[m4] ^ ((ulong)pb[m4 + 1] << 8) ^
((ulong)pb[m4 + 2] << 16) ^ h) * 0x59B8E8939E19695DUL;
break;
default:
Debug.Assert(m4 == m);
break;
u ^= m_vSBox[v[i]];
u *= 3;
}
Debug.Assert((cb != 0) || ((uint)(h >> 32) == hI32));
return (uint)(h >> 32);
}
internal static uint Hash32Ex<T>(T[] v, int iOffset, int c)
{
const ulong hI = 0x4295DC458269ED9DUL;
const uint hI32 = (uint)(hI >> 32);
if (v == null) { Debug.Assert(false); return hI32; }
if (iOffset < 0) { Debug.Assert(false); return hI32; }
if (c < 0) { Debug.Assert(false); return hI32; }
int m = iOffset + c;
if ((m < 0) || (m > v.Length)) { Debug.Assert(false); return hI32; }
ulong h = hI;
for (int i = iOffset; i < m; ++i)
h = (h ^ (uint)v[i].GetHashCode()) * 0x5EA4A1E35C8ACDA3UL;
Debug.Assert((c != 0) || ((uint)(h >> 32) == hI32));
return (uint)(h >> 32);
}
internal static ulong Hash64(int[] v, int iOffset, int ci)
{
ulong h = 0x4295DC458269ED9DUL;
if (v == null) { Debug.Assert(false); return h; }
if (iOffset < 0) { Debug.Assert(false); return h; }
if (ci < 0) { Debug.Assert(false); return h; }
int m = iOffset + ci;
if ((m < 0) || (m > v.Length)) { Debug.Assert(false); return h; }
for (int i = iOffset; i < m; ++i)
h = (h ^ (uint)v[i]) * 0x5EA4A1E35C8ACDA3UL;
return ((h ^ (h >> 32)) * 0x59B8E8939E19695DUL);
return u;
}
public static void CopyStream(Stream sSource, Stream sTarget)
@@ -620,31 +586,20 @@ namespace KeePassLib.Utility
if(sSource == null) throw new ArgumentNullException("sSource");
if(sTarget == null) throw new ArgumentNullException("sTarget");
const int cbBuf = 4096;
byte[] pbBuf = new byte[cbBuf];
const int nBufSize = 4096;
byte[] pbBuf = new byte[nBufSize];
while(true)
{
int cbRead = sSource.Read(pbBuf, 0, cbBuf);
if (cbRead == 0) break;
int nRead = sSource.Read(pbBuf, 0, nBufSize);
if(nRead == 0) break;
sTarget.Write(pbBuf, 0, cbRead);
sTarget.Write(pbBuf, 0, nRead);
}
// Do not close any of the streams
}
public static byte[] Read(Stream s)
{
if (s == null) throw new ArgumentNullException("s");
using (MemoryStream ms = new MemoryStream())
{
CopyStream(s, ms);
return ms.ToArray();
}
}
public static byte[] Read(Stream s, int nCount)
{
if(s == null) throw new ArgumentNullException("s");
@@ -671,17 +626,6 @@ namespace KeePassLib.Utility
return pb;
}
internal static string ReadString(Stream s, Encoding enc)
{
if (s == null) throw new ArgumentNullException("s");
if (enc == null) throw new ArgumentNullException("enc");
using (StreamReader sr = new StreamReader(s, enc, true))
{
return sr.ReadToEnd();
}
}
public static void Write(Stream s, byte[] pbData)
{
if(s == null) { Debug.Assert(false); return; }
@@ -696,6 +640,7 @@ namespace KeePassLib.Utility
if(pbData == null) throw new ArgumentNullException("pbData");
if(pbData.Length == 0) return pbData;
byte[] pbCompressed;
using(MemoryStream msSource = new MemoryStream(pbData, false))
{
using(MemoryStream msCompressed = new MemoryStream())
@@ -703,12 +648,14 @@ namespace KeePassLib.Utility
using(GZipStream gz = new GZipStream(msCompressed,
CompressionMode.Compress))
{
CopyStream(msSource, gz);
MemUtil.CopyStream(msSource, gz);
}
return msCompressed.ToArray();
pbCompressed = msCompressed.ToArray();
}
}
return pbCompressed;
}
public static byte[] Decompress(byte[] pbCompressed)
@@ -716,6 +663,7 @@ namespace KeePassLib.Utility
if(pbCompressed == null) throw new ArgumentNullException("pbCompressed");
if(pbCompressed.Length == 0) return pbCompressed;
byte[] pbData;
using(MemoryStream msData = new MemoryStream())
{
using(MemoryStream msCompressed = new MemoryStream(pbCompressed, false))
@@ -723,12 +671,14 @@ namespace KeePassLib.Utility
using(GZipStream gz = new GZipStream(msCompressed,
CompressionMode.Decompress))
{
CopyStream(gz, msData);
MemUtil.CopyStream(gz, msData);
}
}
return msData.ToArray();
pbData = msData.ToArray();
}
return pbData;
}
public static int IndexOf<T>(T[] vHaystack, T[] vNeedle)
@@ -738,13 +688,10 @@ namespace KeePassLib.Utility
if(vNeedle == null) throw new ArgumentNullException("vNeedle");
if(vNeedle.Length == 0) return 0;
int cN = vNeedle.Length;
int iMax = vHaystack.Length - cN;
for (int i = 0; i <= iMax; ++i)
for(int i = 0; i <= (vHaystack.Length - vNeedle.Length); ++i)
{
bool bFound = true;
for (int m = 0; m < cN; ++m)
for(int m = 0; m < vNeedle.Length; ++m)
{
if(!vHaystack[i + m].Equals(vNeedle[m]))
{
@@ -839,44 +786,6 @@ namespace KeePassLib.Utility
yield break;
}
internal static IEnumerable<T> Distinct<T, TKey>(IEnumerable<T> s,
GFunc<T, TKey> fGetKey, bool bPreferLast)
{
if (s == null) throw new ArgumentNullException("s");
if (fGetKey == null) throw new ArgumentNullException("fGetKey");
Dictionary<TKey, bool> d = new Dictionary<TKey, bool>();
if (bPreferLast)
{
List<T> l = new List<T>(s);
int n = l.Count;
bool[] v = new bool[n];
for (int i = n - 1; i >= 0; --i)
{
TKey k = fGetKey(l[i]);
if (!d.ContainsKey(k)) { d[k] = true; v[i] = true; }
}
for (int i = 0; i < n; ++i)
{
if (v[i]) yield return l[i];
}
}
else
{
foreach (T t in s)
{
TKey k = fGetKey(t);
if (!d.ContainsKey(k)) { d[k] = true; yield return t; }
}
}
yield break;
}
internal static bool ListsEqual<T>(List<T> a, List<T> b)
where T : class, IEquatable<T>
{
@@ -900,164 +809,5 @@ namespace KeePassLib.Utility
return true;
}
internal static int Count(byte[] pb, byte bt)
{
if (pb == null) { Debug.Assert(false); return 0; }
int cb = pb.Length, r = 0;
for (int i = 0; i < cb; ++i)
{
if (pb[i] == bt) ++r;
}
return r;
}
[MethodImpl(MioNoOptimize)]
internal static void DisposeIfPossible(object o)
{
if (o == null) { Debug.Assert(false); return; }
IDisposable d = (o as IDisposable);
if (d != null) d.Dispose();
}
internal static object GetEnumValue(Type tEnum, string strName)
{
if (tEnum == null) { Debug.Assert(false); return null; }
if (!tEnum.IsEnum) { Debug.Assert(false); return null; }
if (string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; }
return ((Array.IndexOf<string>(Enum.GetNames(tEnum), strName) >= 0) ?
Enum.Parse(tEnum, strName) : null);
}
internal static T ConvertObject<T>(object o, T tDefault)
{
if (o == null) return tDefault;
try
{
if (o is T) return (T)o;
return (T)Convert.ChangeType(o, typeof(T));
}
catch (Exception) { Debug.Assert(false); }
try { return (T)o; }
catch (Exception) { Debug.Assert(false); }
return tDefault;
}
internal static T BytesToStruct<T>(byte[] pb, int iOffset)
where T : struct
{
if (pb == null) throw new ArgumentNullException("pb");
if (iOffset < 0) throw new ArgumentOutOfRangeException("iOffset");
int cb = Marshal.SizeOf(typeof(T));
if (cb <= 0) { Debug.Assert(false); return default(T); }
if (iOffset > (pb.Length - cb)) throw new ArgumentOutOfRangeException("iOffset");
IntPtr p = Marshal.AllocCoTaskMem(cb);
if (p == IntPtr.Zero) throw new OutOfMemoryException();
object o;
try
{
Marshal.Copy(pb, iOffset, p, cb);
o = Marshal.PtrToStructure(p, typeof(T));
}
finally { Marshal.FreeCoTaskMem(p); }
return (T)o;
}
internal static byte[] StructToBytes<T>(ref T t)
where T : struct
{
int cb = Marshal.SizeOf(typeof(T));
if (cb <= 0) { Debug.Assert(false); return MemUtil.EmptyByteArray; }
byte[] pb = new byte[cb];
IntPtr p = Marshal.AllocCoTaskMem(cb);
if (p == IntPtr.Zero) throw new OutOfMemoryException();
try
{
Marshal.StructureToPtr(t, p, false);
Marshal.Copy(p, pb, 0, cb);
}
finally { Marshal.FreeCoTaskMem(p); }
return pb;
}
internal static IntPtr AddPtr(IntPtr p, long cb)
{
// IntPtr.operator+ and IntPtr.Add are not available in .NET 2.0
if (IntPtr.Size >= 8)
return new IntPtr(unchecked(p.ToInt64() + cb));
return new IntPtr(unchecked(p.ToInt32() + (int)cb));
}
// Cf. Array.Empty<T>() of .NET 4.6
private static class EmptyArrayEx<T>
{
internal static readonly T[] Instance = new T[0];
}
internal static T[] EmptyArray<T>()
{
return EmptyArrayEx<T>.Instance;
}
}
internal sealed class ArrayHelperEx<T> : IEqualityComparer<T[]>, IComparer<T[]>
where T : IEquatable<T>, IComparable<T>
{
public int GetHashCode(T[] obj)
{
if (obj == null) { Debug.Assert(false); throw new ArgumentNullException("obj"); }
return (int)MemUtil.Hash32Ex<T>(obj, 0, obj.Length);
}
public bool Equals(T[] x, T[] y)
{
if (object.ReferenceEquals(x, y)) return true;
if ((x == null) || (y == null)) return false;
int n = x.Length;
if (n != y.Length) return false;
for (int i = 0; i < n; ++i)
{
if (!x[i].Equals(y[i])) return false;
}
return true;
}
public int Compare(T[] x, T[] y)
{
if (object.ReferenceEquals(x, y)) return 0;
if (x == null) return -1;
if (y == null) return 1;
int n = x.Length, m = y.Length;
if (n != m) return ((n < m) ? -1 : 1);
for (int i = 0; i < n; ++i)
{
T tX = x[i], tY = y[i];
if (!tX.Equals(tY)) return tX.CompareTo(tY);
}
return 0;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
/*
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
/// </summary>
/// This also contains methods which are UI specific and should be replacable for testing.
public interface IKp2aApp : ICertificateValidationHandler
public interface IKp2aApp : ICertificateValidationHandler, IActiveContextProvider
{
/// <summary>
/// Locks all currently open databases, quicklocking if available (unless false is passed for allowQuickUnlock)
@@ -52,7 +52,9 @@ namespace keepass2android
/// <summary>
/// Loads the specified data as the currently open database, as unlocked.
/// </summary>
Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent);
Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey,
IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent,
IDatabaseModificationWatcher modificationWatcher);
HashSet<PwGroup> DirtyGroups { get; }
@@ -96,7 +98,6 @@ namespace keepass2android
EventHandler<DialogClickEventArgs> yesHandler,
EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler,
Context ctx,
string messageSuffix = "");
/// <summary>
@@ -107,7 +108,6 @@ namespace keepass2android
EventHandler<DialogClickEventArgs> yesHandler,
EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler,
Context ctx,
string messageSuffix = "");
void ShowMessage(Context ctx, int resourceId, MessageSeverity severity);
@@ -136,10 +136,17 @@ namespace keepass2android
bool CheckForDuplicateUuids { get; }
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
ICertificateErrorHandler CertificateErrorHandler { get; }
#endif
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,8 +186,11 @@ namespace keepass2android.Io
Kp2aLog.Log("couldn't open from remote " + ioc.Path);
#endif
Kp2aLog.Log(ex.ToString());
if (TriggerWarningWhenFallingBackToCache)
{
_cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
}
return File.OpenRead(cachedFilePath);
}
}
@@ -327,7 +330,10 @@ namespace keepass2android.Io
Kp2aLog.Log("couldn't save to remote " + ioc.Path);
Kp2aLog.Log(e.ToString());
//notify the supervisor so it might display a warning or schedule a retry
_cacheSupervisor.CouldntSaveToRemote(ioc, e);
if (TriggerWarningWhenFallingBackToCache)
{
_cacheSupervisor.CouldntSaveToRemote(ioc, e); }
return false;
}
}
@@ -632,6 +638,8 @@ namespace keepass2android.Io
set { _cachedStorage.IsOffline = value; }
}
public bool TriggerWarningWhenFallingBackToCache { get; set; }
public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
string[] permissions, Permission[] grantResults)
{

View File

@@ -0,0 +1,13 @@
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

@@ -1,27 +0,0 @@
<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,6 +111,11 @@ namespace keepass2android.Io
}
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)
{
var ex = new Exception(exception.LocalizedMessage ??

View File

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

View File

@@ -11,6 +11,7 @@ namespace keepass2android.Io
public interface IOfflineSwitchable
{
bool IsOffline { get; set; }
bool TriggerWarningWhenFallingBackToCache { get; set; }
}
/// <summary>
@@ -21,6 +22,7 @@ namespace keepass2android.Io
{
private readonly IFileStorage _baseStorage;
public bool IsOffline { get; set; }
public bool TriggerWarningWhenFallingBackToCache { get; set; }
public OfflineSwitchableFileStorage(IFileStorage baseStorage)
{

View File

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

View File

@@ -0,0 +1,239 @@
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,43 +22,97 @@ using KeePassLib.Interfaces;
namespace keepass2android
{
public interface IKp2aStatusLogger : IStatusLogger
{
void UpdateMessage(UiStringKey stringKey);
string LastMessage { get; }
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 ProgressDialogStatusLogger: IStatusLogger {
public class ProgressDialogUi: IProgressUi
{
private readonly IProgressDialog _progressDialog;
readonly IKp2aApp _app;
private readonly Handler _handler;
private string _message = "";
private string _submessage;
private readonly IKp2aApp _app;
public String SubMessage => _submessage;
public String Message => _message;
public String LastSubMessage => _submessage;
public String LastMessage => _message;
public ProgressDialogStatusLogger() {
}
public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) {
public ProgressDialogUi(IKp2aApp app, Handler handler, IProgressDialog pd)
{
_app = app;
_progressDialog = pd;
_handler = handler;
}
public void UpdateMessage(UiStringKey stringKey) {
if (_app != null)
UpdateMessage(_app.GetResourceString(stringKey));
}
public void UpdateMessage (String message)
{
Kp2aLog.Log("status message: " + message);
_message = message;
if ( _app!= null && _progressDialog != null && _handler != null ) {
_handler.Post(() => {_progressDialog.SetMessage(message); } );
}
}
public void UpdateSubMessage(String submessage)
{
Kp2aLog.Log("status submessage: " + submessage);
@@ -80,58 +134,40 @@ namespace keepass2android
}
}
#region IStatusLogger implementation
public void StartLogging (string strOperation, bool bWriteOperationToLog)
public void Show()
{
}
public void EndLogging ()
_handler.Post(() =>
{
_progressDialog?.Show();
});
}
public bool SetProgress (uint uPercent)
public void Hide()
{
return true;
_handler.Post(() =>
{
_progressDialog?.Dismiss();
});
}
public bool SetText (string strNewText, LogStatusType lsType)
public void UpdateMessage(string message)
{
if (strNewText.StartsWith("KP2AKEY_"))
Kp2aLog.Log("status message: " + message);
_message = message;
if (_app != null && _progressDialog != null && _handler != null)
{
UiStringKey key;
if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key))
_handler.Post(() =>
{
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);
_progressDialog.SetMessage(message);
});
}
}
public bool ContinueWork ()
{
return true;
}
#endregion
}
}

View File

@@ -1,179 +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;
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

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

View File

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

View File

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

View File

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

View File

@@ -4,35 +4,41 @@ using System.IO;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using KeePassLib.Serialization;
using keepass2android.Io;
using KeePass.Util;
using Group.Pals.Android.Lib.UI.Filechooser.Utils;
using KeePassLib;
namespace keepass2android
{
public class SynchronizeCachedDatabase: RunnableOnFinish
public class SynchronizeCachedDatabase: OperationWithFinishHandler
{
private readonly Activity _context;
private readonly IKp2aApp _app;
private SaveDb _saveDb;
private IDatabaseModificationWatcher _modificationWatcher;
private readonly Database _database;
public SynchronizeCachedDatabase(Activity context, IKp2aApp app, OnFinish finish)
: base(context, finish)
public SynchronizeCachedDatabase(IKp2aApp app, Database database, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher)
: base(app, operationFinishedHandler)
{
_context = context;
_app = app;
_database = database;
_modificationWatcher = modificationWatcher;
}
public override void Run()
{
try
{
IOConnectionInfo ioc = _app.CurrentDb.Ioc;
IOConnectionInfo ioc = _database.Ioc;
IFileStorage fileStorage = _app.GetFileStorage(ioc);
if (!(fileStorage is CachingFileStorage))
{
throw new Exception("Cannot sync a non-cached database!");
}
StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase);
CachingFileStorage cachingFileStorage = (CachingFileStorage)fileStorage;
@@ -49,7 +55,8 @@ namespace keepass2android
catch (FileNotFoundException)
{
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
cachingFileStorage.UpdateRemoteFile(ioc,
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
Kp2aLog.Log("Checking for file change: file not found");
return;
@@ -64,7 +71,8 @@ namespace keepass2android
if (cachingFileStorage.HasLocalChanges(ioc))
{
//conflict! need to merge
_saveDb = new SaveDb(_context, _app, new ActionOnFinish(ActiveActivity, (success, result, activity) =>
var _saveDb = new SaveDb(_app, new ActionOnOperationFinished(_app,
(success, result, activity) =>
{
if (!success)
{
@@ -74,19 +82,43 @@ namespace keepass2android
{
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
}
_saveDb = null;
}), _app.CurrentDb, false, remoteData);
}), _database, false, remoteData, _modificationWatcher);
_saveDb.SetStatusLogger(StatusLogger);
_saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
_saveDb.SyncInBackground = false;
_saveDb.Run();
_app.CurrentDb.UpdateGlobals();
_database.UpdateGlobals();
_app.MarkAllGroupsAsDirty();
}
else
{
//only the remote file was modified -> reload database.
//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));
var onFinished = new ActionOnOperationFinished(_app, (success, result, activity) =>
{
if (!success)
{
Finish(false, result);
}
else
{
new Handler(Looper.MainLooper).Post(() =>
{
_database.UpdateGlobals();
_app.MarkAllGroupsAsDirty();
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
});
}
});
var _loadDb = new LoadDb(_app, ioc, Task.FromResult(remoteData),
_database.KpDatabase.MasterKey, null, onFinished, true, false, _modificationWatcher);
_loadDb.SetStatusLogger(StatusLogger);
_loadDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
_loadDb.Run();
}
}
else
@@ -96,7 +128,8 @@ namespace keepass2android
{
//but we have local changes -> upload:
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
cachingFileStorage.UpdateRemoteFile(ioc,
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
StatusLogger.UpdateSubMessage("");
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
}
@@ -106,6 +139,16 @@ namespace keepass2android
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)
{
@@ -115,10 +158,5 @@ namespace keepass2android
}
public void JoinWorkerThread()
{
if (_saveDb != null)
_saveDb.JoinWorkerThread();
}
}
}

View File

@@ -1,57 +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.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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
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 UiStringKey _statusMessage;
public DeleteEntry(Activity activiy, IKp2aApp app, PwEntry entry, OnFinish finish):base(activiy, finish, app) {
Ctx = activiy;
public DeleteEntry(IKp2aApp app, PwEntry entry, OnOperationFinishedHandler operationFinishedHandler):base(operationFinishedHandler, app) {
Db = app.FindDatabaseForElement(entry);
_entry = entry;

View File

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

View File

@@ -12,11 +12,11 @@ namespace keepass2android
private readonly List<IStructureItem> _elementsToDelete;
private readonly bool _canRecycle;
public DeleteMultipleItemsFromOneDatabase(Activity activity, Database db, List<IStructureItem> elementsToDelete, OnFinish finish, IKp2aApp app)
: base(activity, finish, app)
public DeleteMultipleItemsFromOneDatabase(Activity activity, Database db, List<IStructureItem> elementsToDelete, OnOperationFinishedHandler operationFinishedHandler, IKp2aApp app)
: base(operationFinishedHandler, app)
{
_elementsToDelete = elementsToDelete;
SetMembers(activity, db);
SetMembers(db);
//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)

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
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,25 +21,33 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Android.App;
using Android.OS;
using KeePass.Util;
using keepass2android.database.edit;
using keepass2android.Io;
using KeePassLib;
using KeePassLib.Keys;
using KeePassLib.Serialization;
namespace keepass2android
{
public class LoadDb : RunnableOnFinish {
public class LoadDb : OperationWithFinishHandler {
private readonly IOConnectionInfo _ioc;
private readonly Task<MemoryStream> _databaseData;
private readonly CompositeKey _compositeKey;
private readonly string _keyfileOrProvider;
private readonly string? _keyfileOrProvider;
private readonly IKp2aApp _app;
private readonly bool _rememberKeyfile;
IDatabaseFormat _format;
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 bool DoNotSetStatusLoggerMessage = false;
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;
_ioc = ioc;
_databaseData = databaseData;
@@ -47,14 +55,13 @@ namespace keepass2android
_keyfileOrProvider = keyfileOrProvider;
_updateLastUsageTimestamp = updateLastUsageTimestamp;
_makeCurrent = makeCurrent;
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
}
protected bool success = false;
private bool _updateLastUsageTimestamp;
private readonly bool _makeCurrent;
private readonly IDatabaseModificationWatcher _modificationWatcher;
public override void Run()
{
@@ -66,15 +73,36 @@ namespace keepass2android
SaveFileData(_ioc, _keyfileOrProvider);
var fileStorage = _app.GetFileStorage(_ioc);
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:
MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
MemoryStream databaseStream;
if (preloadedMemoryStream != null)
{
//note: if the stream has been loaded already, we don't need to trigger another sync later on
databaseStream = preloadedMemoryStream;
}
else
{
using (Stream s = _app.GetFileStorage(_ioc).OpenFileForRead(_ioc))
if (_app.SyncInBackgroundPreference && fileStorage is CachingFileStorage cachingFileStorage &&
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();
s.CopyTo(databaseStream);
@@ -82,8 +110,13 @@ 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:
_format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(_app.GetFileStorage(_ioc).GetFileExtension(_ioc)));
_format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(fileStorage.GetFileExtension(_ioc)));
TryLoad(databaseStream);
@@ -120,6 +153,12 @@ namespace keepass2android
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
return;
}
catch (Java.Lang.InterruptedException)
{
Kp2aLog.Log("Load interrupted");
//close without Finish()
return;
}
catch (Exception e)
{
if (!(e is InvalidCompositeKeyException))
@@ -131,6 +170,8 @@ namespace keepass2android
}
public bool RequiresSubsequentSync { get; set; } = false;
/// <summary>
/// Holds the exception which was thrown during execution (if any)
/// </summary>
@@ -138,7 +179,6 @@ namespace keepass2android
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
//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.
@@ -147,11 +187,16 @@ namespace keepass2android
workingCopy.Seek(0, SeekOrigin.Begin);
//reset stream if we need to reuse it later:
databaseStream.Seek(0, SeekOrigin.Begin);
Kp2aLog.Log("LoadDb: Ready to start loading");
if (!StatusLogger.ContinueWork())
{
throw new Java.Lang.InterruptedException();
}
//now let's go:
try
{
Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent);
Database newDb =
_app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent, _modificationWatcher);
Kp2aLog.Log("LoadDB OK");
Finish(true, _format.SuccessMessage);

View File

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

View File

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

View File

@@ -0,0 +1,69 @@
/*
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

@@ -1,78 +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;
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,56 +30,68 @@ using keepass2android.Io;
using Debug = System.Diagnostics.Debug;
using Exception = System.Exception;
using KeePass.Util;
using Thread = System.Threading.Thread;
namespace keepass2android
{
public class SaveDb : RunnableOnFinish {
/// <summary>
/// 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 Database _db;
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>
/// 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>
private readonly Stream _streamForOrigFile;
private readonly Context _ctx;
private Java.Lang.Thread _workerThread;
public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish, bool dontSave)
: base(ctx, finish)
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler, bool dontSave, IDatabaseModificationWatcher modificationWatcher)
: base(app, operationFinishedHandler)
{
_db = db;
_ctx = ctx;
_app = app;
_dontSave = dontSave;
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
}
/// <summary>
/// Constructor for sync
/// </summary>
/// <param name="ctx"></param>
/// <param name="app"></param>
/// <param name="finish"></param>
/// <param name="operationFinishedHandler"></param>
/// <param name="dontSave"></param>
/// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param>
public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish, Database db, bool dontSave, Stream streamForOrigFile)
: base(ctx, finish)
public SaveDb(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, Database db, bool dontSave, Stream streamForOrigFile, IDatabaseModificationWatcher modificationWatcher = null)
: base(app, operationFinishedHandler)
{
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
_db = db;
_ctx = ctx;
_app = app;
_dontSave = dontSave;
_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;
_db = db;
_dontSave = false;
SyncInBackground = _app.SyncInBackgroundPreference;
}
public bool ShowDatabaseIocInStatus { get; set; }
@@ -103,29 +115,42 @@ namespace keepass2android
if (ShowDatabaseIocInStatus)
message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
if (!DoNotSetStatusLoggerMessage)
{
StatusLogger.UpdateMessage(message);
}
IOConnectionInfo ioc = _db.Ioc;
IFileStorage fileStorage = _app.GetFileStorage(ioc);
if (SyncInBackground && fileStorage is IOfflineSwitchable offlineSwitchable)
{
offlineSwitchable.IsOffline = true;
//no warning. We'll trigger a sync later.
offlineSwitchable.TriggerWarningWhenFallingBackToCache = false;
requiresSubsequentSync = true;
}
if (_streamForOrigFile == null)
{
if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave))
|| (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving
{
PerformSaveWithoutCheck(fileStorage, ioc);
Finish(true);
FinishWithSuccess();
return;
}
}
bool hasStreamForOrigFile = (_streamForOrigFile != null);
bool hasChangeFast = hasStreamForOrigFile ||
fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection;
bool hasHashChanged = hasChangeFast ||
bool hasHashChanged = !requiresSubsequentSync && (
hasChangeFast ||
(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)
{
@@ -158,15 +183,14 @@ namespace keepass2android
RunInWorkerThread(() =>
{
PerformSaveWithoutCheck(fileStorage, ioc);
Finish(true);
FinishWithSuccess();
});
},
//cancel
(sender, args) =>
{
RunInWorkerThread(() => Finish(false));
},
_ctx
}
);
}
@@ -174,7 +198,7 @@ namespace keepass2android
else
{
PerformSaveWithoutCheck(fileStorage, ioc);
Finish(true);
FinishWithSuccess();
}
}
@@ -194,21 +218,66 @@ namespace keepass2android
}
else
{
Finish(true);
FinishWithSuccess();
}
}
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)
{
//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
//small.
MergeIn(fileStorage, ioc);
PerformSaveWithoutCheck(fileStorage, ioc);
_db.UpdateGlobals();
Finish(true);
try
{
_modificationWatcher.BeforeModifyDatabases();
}
catch (Java.Lang.InterruptedException)
{
// leave without Finish()
return;
}
try
{
MergeIn(fileStorage, ioc);
}
finally
{
_modificationWatcher.AfterModifyDatabases();
}
PerformSaveWithoutCheck(fileStorage, ioc);
new Handler(Looper.MainLooper).Post(() =>
{
_db.UpdateGlobals();
});
FinishWithSuccess();
}
private void RunInWorkerThread(Action runHandler)
{
@@ -282,7 +351,7 @@ namespace keepass2android
private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc)
{
StatusLogger.UpdateSubMessage("");
_db.SaveData();
_db.SaveData(fileStorage);
_db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
}

View File

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

View File

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

View File

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

View File

@@ -1,22 +0,0 @@
#!/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

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -31,8 +31,6 @@ namespace keepass2android
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title));
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
#if !NoNet
,Resource.Array.ChangeLog_1_12_net

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,7 @@ using Android.Util;
using AndroidX.Core.Content;
using Google.Android.Material.Dialog;
using keepass2android;
using keepass2android.views;
namespace keepass2android
{
@@ -63,7 +64,7 @@ namespace keepass2android
{
private readonly string _binaryToSave;
public ExportBinaryProcessManager(int requestCode, Activity activity, string key) : base(requestCode, activity)
public ExportBinaryProcessManager(int requestCode, LifecycleAwareActivity activity, string key) : base(requestCode, activity)
{
_binaryToSave = key;
}
@@ -75,13 +76,13 @@ namespace keepass2android
protected override void SaveFile(IOConnectionInfo ioc)
{
var task = new EntryActivity.WriteBinaryTask(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) =>
var task = new EntryActivity.WriteBinaryTask(App.Kp2a, new ActionOnOperationFinished(App.Kp2a, (success, message, context) =>
{
if (!success)
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
}
), ((EntryActivity)_activity).Entry.Binaries.Get(_binaryToSave), ioc);
ProgressTask pt = new ProgressTask(App.Kp2a, _activity, task);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, task);
pt.Run();
}
@@ -89,6 +90,7 @@ namespace keepass2android
public override void OnSaveInstanceState(Bundle outState)
{
outState.PutString("BinaryToSave", _binaryToSave);
base.OnSaveInstanceState(outState);
}
@@ -97,7 +99,7 @@ namespace keepass2android
[Activity (Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden,
Theme = "@style/Kp2aTheme_ActionBar")]
public class EntryActivity : LockCloseActivity
public class EntryActivity : LockCloseActivity, IProgressUiProvider
{
public const String KeyEntry = "entry";
public const String KeyRefreshPos = "refresh_pos";
@@ -110,6 +112,45 @@ namespace keepass2android
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)
{
Intent i = new Intent(act, typeof(EntryActivity));
@@ -481,8 +522,8 @@ namespace keepass2android
Entry.Expires = true;
Entry.Touch(true);
RequiresRefresh();
UpdateEntry update = new UpdateEntry(this, App.Kp2a, backupEntry, Entry, null);
ProgressTask pt = new ProgressTask(App.Kp2a, this, update);
UpdateEntry update = new UpdateEntry(App.Kp2a, backupEntry, Entry, null);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, update);
pt.Run();
}
FillData();
@@ -501,6 +542,12 @@ namespace keepass2android
//the rest of the things to do depends on the current app task:
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()
@@ -525,13 +572,17 @@ namespace keepass2android
App.Kp2a.DirtyGroups.Add(parent);
}
var saveTask = new SaveDb(this, App.Kp2a, App.Kp2a.FindDatabaseForElement(Entry), new ActionOnFinish(this, (success, message, activity) =>
var saveTask = new SaveDb( App.Kp2a, App.Kp2a.FindDatabaseForElement(Entry), new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) =>
{
if (context is Activity activity)
{
activity.SetResult(KeePass.ExitRefresh);
activity.Finish();
}
}));
ProgressTask pt = new ProgressTask(App.Kp2a, this, saveTask);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, saveTask);
pt.Run();
}
@@ -1078,6 +1129,8 @@ namespace keepass2android
UnregisterReceiver(_pluginActionReceiver);
if (_pluginFieldReceiver != null)
UnregisterReceiver(_pluginFieldReceiver);
if (_dataUpdatedIntentReceiver != null)
UnregisterReceiver(_dataUpdatedIntentReceiver);
base.OnDestroy();
}
@@ -1260,13 +1313,13 @@ namespace keepass2android
}
public class WriteBinaryTask : RunnableOnFinish
public class WriteBinaryTask : OperationWithFinishHandler
{
private readonly IKp2aApp _app;
private readonly ProtectedBinary _data;
private IOConnectionInfo _targetIoc;
public WriteBinaryTask(Activity activity, IKp2aApp app, OnFinish onFinish, ProtectedBinary data, IOConnectionInfo targetIoc) : base(activity, onFinish)
public WriteBinaryTask(IKp2aApp app, OnOperationFinishedHandler onOperationFinishedHandler, ProtectedBinary data, IOConnectionInfo targetIoc) : base(app, onOperationFinishedHandler)
{
_app = app;
_data = data;
@@ -1354,6 +1407,7 @@ namespace keepass2android
}
bool isPaused = false;
private UpdateEntryActivityBroadcastReceiver _dataUpdatedIntentReceiver;
protected override void OnPause()
{
@@ -1440,8 +1494,8 @@ namespace keepass2android
Finish();
return true;
case Resource.Id.menu_delete:
DeleteEntry task = new DeleteEntry(this, App.Kp2a, Entry,
new ActionOnFinish(this, (success, message, activity) => { if (success) { RequiresRefresh(); Finish();}}));
DeleteEntry task = new DeleteEntry(App.Kp2a, Entry,
new ActionOnOperationFinished(App.Kp2a, (success, message, context) => { if (success) { RequiresRefresh(); Finish();}}));
task.Start();
break;
case Resource.Id.menu_toggle_pass:
@@ -1504,16 +1558,16 @@ namespace keepass2android
//save the entry:
ActionOnFinish closeOrShowError = new ActionOnFinish(this, (success, message, activity) =>
ActionOnOperationFinished closeOrShowError = new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) =>
{
OnFinish.DisplayMessage(this, message, true);
finishAction((EntryActivity)activity);
OnOperationFinishedHandler.DisplayMessage(this, message, true);
finishAction(context as EntryActivity);
});
RunnableOnFinish runnable = new UpdateEntry(this, App.Kp2a, initialEntry, newEntry, closeOrShowError);
OperationWithFinishHandler runnable = new UpdateEntry(App.Kp2a, initialEntry, newEntry, closeOrShowError);
ProgressTask pt = new ProgressTask(App.Kp2a, this, runnable);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, runnable);
pt.Run();
}
@@ -1546,10 +1600,10 @@ namespace keepass2android
string url = _stringViews[urlFieldKey].Text;
if (url == null) return false;
// Default https:// if no protocol specified
// Default http:// if no protocol specified
if ((!url.Contains(":") || (url.StartsWith("www."))))
{
url = "https://" + url;
url = "http://" + url;
}
try
@@ -1603,5 +1657,7 @@ namespace keepass2android
imageViewerIntent.PutExtra("EntryKey", key);
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>
/// Holds the state of the EntrryEditActivity. This is required to be able to keep a partially modified entry in memory
/// Holds the state of the EntryEditActivity. 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
/// when there are big attachements.
/// </summary>

View File

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

View File

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

View File

@@ -667,10 +667,10 @@ namespace keepass2android
return true;
}
private void IocSelected(Activity activity, IOConnectionInfo ioc)
private void IocSelected(Context context, IOConnectionInfo ioc)
{
if (OnOpen != null)
OnOpen(activity, ioc);
OnOpen(context, ioc);
}
public bool StartFileChooser(string defaultPath)
@@ -781,7 +781,7 @@ namespace keepass2android
}
else
{
var task = new CreateNewFilename(activity, new ActionOnFinish(activity, (success, messageOrFilename, newActivity) =>
var task = new CreateNewFilename(App.Kp2a, new ActionOnOperationFinished(App.Kp2a, (success, messageOrFilename, newActivity) =>
{
if (!success)
{
@@ -793,7 +793,7 @@ namespace keepass2android
}), filename);
new ProgressTask(App.Kp2a, activity, task).Run();
new BlockingOperationStarter(App.Kp2a, task).Run();
}
}

View File

@@ -23,7 +23,6 @@ using Android.App;
using Android.App.Admin;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics;
using Android.OS;
using Android.Preferences;
@@ -47,6 +46,8 @@ namespace keepass2android
#endif
{
public const string GeneratedPasswordKey = "keepass2android.password.generated_password";
private readonly int[] _buttonLengthButtonIds = new[] {Resource.Id.btn_length6,
Resource.Id.btn_length8,
Resource.Id.btn_length12,
@@ -67,13 +68,8 @@ namespace keepass2android
Resource.Id.cb_exclude_lookalike
};
PasswordFont _passwordFont = new PasswordFont();
private static object _popularPasswordsLock = new object();
private static bool _popularPasswordsInitialized = false;
private ActivityDesign _design;
public GeneratePasswordActivity()
@@ -265,7 +261,7 @@ namespace keepass2android
EditText password = (EditText) FindViewById(Resource.Id.password_edit);
Intent intent = new Intent();
intent.PutExtra("keepass2android.password.generated_password", password.Text);
intent.PutExtra(GeneratedPasswordKey, password.Text);
SetResult(KeePass.ResultOkPasswordGenerator, intent);
@@ -308,10 +304,6 @@ namespace keepass2android
EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit);
txtPasswordToSet.TextChanged += (sender, args) =>
{
Task.Run(() => UpdatePasswordStrengthEstimate(txtPasswordToSet.Text));
};
_passwordFont.ApplyTo(txtPasswordToSet);
@@ -477,48 +469,17 @@ namespace keepass2android
return;
String password = "";
uint passwordBits = 0;
Task.Run(() =>
{
password = GeneratePassword();
passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray());
RunOnUiThread(() =>
{
EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit);
txtPassword.Text = password;
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;
@@ -544,7 +505,13 @@ namespace keepass2android
PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
UpdateProfileSpinnerSelection();
});
});
}
private void UpdateProfileSpinnerSelection()

View File

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

View File

@@ -43,11 +43,13 @@ using keepass2android;
using KeeTrayTOTP.Libraries;
using AndroidX.AppCompat.Widget;
using Google.Android.Material.Dialog;
using keepass2android.views;
using SearchView = AndroidX.AppCompat.Widget.SearchView;
using AndroidX.Core.Content;
namespace keepass2android
{
public abstract class GroupBaseActivity : LockCloseActivity
public abstract class GroupBaseActivity : LockCloseActivity, IProgressUiProvider
{
public const String KeyEntry = "entry";
public const String KeyMode = "mode";
@@ -201,19 +203,18 @@ namespace keepass2android
new PwUuid(MemUtil.HexStringToByteArray(data.Extras.GetString(GroupEditActivity.KeyCustomIconId)));
String strGroupUuid = data.Extras.GetString(GroupEditActivity.KeyGroupUuid);
GroupBaseActivity act = this;
Handler handler = new Handler();
RunnableOnFinish task;
OperationWithFinishHandler task;
if (strGroupUuid == null)
{
task = AddGroup.GetInstance(this, App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, new RefreshTask(handler, this), false);
task = AddGroup.GetInstance(App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, CreateRefreshAction(), false);
}
else
{
PwUuid groupUuid = new PwUuid(MemUtil.HexStringToByteArray(strGroupUuid));
task = new EditGroup(this, App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.FindGroup(groupUuid),
new RefreshTask(handler, this));
task = new EditGroup(App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.FindGroup(groupUuid),
CreateRefreshAction());
}
ProgressTask pt = new ProgressTask(App.Kp2a, act, task);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, task);
pt.Run();
}
@@ -274,6 +275,7 @@ namespace keepass2android
private IMenuItem searchItem;
private IMenuItem searchItemDummy;
private bool isPaused;
private UpdateGroupBaseActivityBroadcastReceiver _dataUpdatedIntentReceiver;
protected override void OnResume()
{
@@ -745,9 +747,10 @@ 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);
@@ -894,15 +897,10 @@ namespace keepass2android
RegisterInfoTextDisplay(
"DbReadOnly"); //this ensures that we don't show the general info texts too soon
var infotext_view = FindViewById<TextView>(Resource.Id.dbreadonly_infotext_text);
if (infotext_view != null)
{
infotext_view.Text =
FindViewById<TextView>(Resource.Id.dbreadonly_infotext_text).Text =
(GetString(Resource.String.FileReadOnlyMessagePre) + " " +
App.Kp2a.GetResourceString(reason.Result));
}
}
}
UpdateBottomBarElementVisibility(Resource.Id.dbreadonly_infotext, canShow);
}
@@ -929,14 +927,14 @@ namespace keepass2android
var moveElement = new MoveElements(elementsToMove.ToList(), Group, this, App.Kp2a, new ActionOnFinish(this,
(success, message, activity) =>
var moveElement = new MoveElements(elementsToMove.ToList(), Group, App.Kp2a, new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a,
(success, message, context) =>
{
((GroupBaseActivity)activity)?.StopMovingElements();
(context as GroupBaseActivity)?.StopMovingElements();
if (!String.IsNullOrEmpty(message))
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
}));
var progressTask = new ProgressTask(App.Kp2a, this, moveElement);
var progressTask = new BlockingOperationStarter(App.Kp2a, moveElement);
progressTask.Run();
}
@@ -1038,6 +1036,13 @@ namespace keepass2android
}
}
protected override void OnDestroy()
{
UnregisterReceiver(_dataUpdatedIntentReceiver);
base.OnDestroy();
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
@@ -1217,7 +1222,7 @@ namespace keepass2android
return true;
case Resource.Id.menu_sync:
new SyncUtil(this).SynchronizeDatabase(() => { });
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
return true;
case Resource.Id.menu_work_offline:
@@ -1228,7 +1233,7 @@ namespace keepass2android
case Resource.Id.menu_work_online:
App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false;
UpdateOfflineModeMenu();
new SyncUtil(this).SynchronizeDatabase(() => { });
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
return true;
case Resource.Id.menu_open_other_db:
AppTask.SetActivityResult(this, KeePass.ExitLoadAnotherDb);
@@ -1298,51 +1303,7 @@ 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)
{
@@ -1413,6 +1374,79 @@ namespace keepass2android
{
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
@@ -1475,12 +1509,12 @@ namespace keepass2android
{
return false;
}
Handler handler = new Handler();
switch (item.ItemId)
{
case Resource.Id.menu_delete:
DeleteMultipleItems((GroupBaseActivity)Activity, checkedItems, new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a);
DeleteMultipleItems((GroupBaseActivity)Activity, checkedItems, ((GroupBaseActivity)Activity).CreateRefreshAction(), App.Kp2a);
break;
case Resource.Id.menu_move:
var navMove = new NavigateToFolderAndLaunchMoveElementTask(App.Kp2a.CurrentDb, checkedItems.First().ParentGroup, checkedItems.Select(i => i.Uuid).ToList(), ((GroupBaseActivity)Activity).IsSearchResult);
@@ -1488,10 +1522,10 @@ namespace keepass2android
break;
case Resource.Id.menu_copy:
var copyTask = new CopyEntry((GroupBaseActivity)Activity, App.Kp2a, (PwEntry)checkedItems.First(),
new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a.CurrentDb);
var copyTask = new CopyEntry(App.Kp2a, (PwEntry)checkedItems.First(),
((GroupBaseActivity)Activity).CreateRefreshAction(), App.Kp2a.CurrentDb);
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, copyTask);
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, copyTask);
pt.Run();
break;
@@ -1636,7 +1670,7 @@ namespace keepass2android
}
public void DeleteMultipleItems(GroupBaseActivity activity, List<IStructureItem> checkedItems, OnFinish onFinish, Kp2aApp app)
public void DeleteMultipleItems(GroupBaseActivity activity, List<IStructureItem> checkedItems, OnOperationFinishedHandler onOperationFinishedHandler, Kp2aApp app)
{
if (checkedItems.Any() == false)
return;
@@ -1667,30 +1701,30 @@ namespace keepass2android
}
int dbIndex = 0;
Action<bool, string, Activity> action = null;
action = (success, message, activeActivity) =>
Action<bool, string, Context> action = null;
action = (success, message, context) =>
{
if (success)
{
dbIndex++;
if (dbIndex == itemsForDatabases.Count)
{
onFinish.SetResult(true);
onFinish.Run();
onOperationFinishedHandler.SetResult(true);
onOperationFinishedHandler.Run();
return;
}
new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key,
itemsForDatabases[dbIndex].Value, new ActionOnFinish(activeActivity, (b, s, activity1) => action(b, s, activity1)), app)
itemsForDatabases[dbIndex].Value, new ActionOnOperationFinished(App.Kp2a, (b, s, activity1) => action(b, s, activity1)), app)
.Start();
}
else
{
onFinish.SetResult(false, message, true, null);
onOperationFinishedHandler.SetResult(false, message, true, null);
}
};
new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key,
itemsForDatabases[dbIndex].Value, new ActionOnFinish(activity, (b, s, activity1) => action(b, s, activity1)), app)
itemsForDatabases[dbIndex].Value, new ActionOnOperationFinished(App.Kp2a, (b, s, activity1) => action(b, s, activity1)), app)
.Start();
}

View File

@@ -25,7 +25,7 @@ using Android.Runtime;
namespace keepass2android
{
public abstract class LifecycleAwareActivity : AndroidX.AppCompat.App.AppCompatActivity
public abstract class LifecycleAwareActivity : AndroidX.AppCompat.App.AppCompatActivity, IContextInstanceIdProvider
{
protected override void AttachBaseContext(Context baseContext)
{
@@ -84,12 +84,11 @@ namespace keepass2android
return baseRes;
}
public Action? OnResumeListener { get; set; }
protected override void OnResume()
{
base.OnResume();
OnResumeListener?.Invoke();
App.Kp2a.PerformPendingActions(_instanceId);
Kp2aLog.Log(ClassName + ".OnResume " + ID);
if (App.Kp2a.CurrentDb == null)
@@ -104,22 +103,34 @@ namespace keepass2android
protected override void OnStart()
{
ProgressTask.SetNewActiveActivity(this);
App.Kp2a.ActiveContext = this;
base.OnStart();
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)
{
base.OnCreate(bundle);
_instanceId = bundle?.GetInt(ID_KEY, InvalidId) ?? InvalidId;
if (_instanceId == InvalidId)
{
_instanceId = _nextContextInstanceId++;
}
OnCreateListener?.Invoke(bundle);
Kp2aLog.Log(ClassName + ".OnCreate" + " " + ID);
Kp2aLog.Log(ClassName + ".OnCreate" + " " + ID + " (instance=" + _instanceId +")");
Kp2aLog.Log(ClassName + ":apptask=" + Intent.GetStringExtra("KP2A_APP_TASK_TYPE") + " " + ID);
}
protected override void OnDestroy()
{
base.OnDestroy();
@@ -136,14 +147,18 @@ namespace keepass2android
{
base.OnStop();
Kp2aLog.Log(ClassName + ".OnStop" + " " + ID);
ProgressTask.RemoveActiveActivity(this);
}
protected override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
outState.PutInt(ID_KEY, _instanceId);
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 IOConnectionInfo _ioc;
private BroadcastReceiver _intentReceiver;
private BroadcastReceiver _lockCloseIntentReceiver;
private ActivityDesign _design;
public LockCloseActivity()
@@ -66,11 +66,11 @@ namespace keepass2android
if (Intent.GetBooleanExtra(NoLockCheck, false))
return;
_intentReceiver = new LockCloseActivityBroadcastReceiver(this);
_lockCloseIntentReceiver = new LockCloseActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter();
filter.AddAction(Intents.DatabaseLocked);
filter.AddAction(Intent.ActionScreenOff);
ContextCompat.RegisterReceiver(this, _intentReceiver, filter, (int)ReceiverFlags.Exported);
ContextCompat.RegisterReceiver(this, _lockCloseIntentReceiver, filter, (int)ReceiverFlags.Exported);
}
protected override void OnDestroy()
@@ -79,7 +79,7 @@ namespace keepass2android
{
try
{
UnregisterReceiver(_intentReceiver);
UnregisterReceiver(_lockCloseIntentReceiver);
}
catch (Exception ex)
{

View File

@@ -266,6 +266,7 @@ 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.FOREGROUND_SERVICE" />
<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.MANAGE_ACCOUNTS" android:maxSdkVersion="22" />
<uses-permission android:name="keepass2android.keepass2android_debug.permission.KP2aInternalFileBrowsing" />

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="222"
android:versionName="1.13-r0"
android:versionCode="206"
android:versionName="1.12-r5"
package="keepass2android.keepass2android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
@@ -46,10 +46,12 @@
<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" />
<application android:label="keepass2android"
android:icon="@mipmap/ic_launcher_online"
android:roundIcon="@mipmap/ic_launcher_online_round"
android:networkSecurityConfig="@xml/network_security_config"
>
<meta-data
@@ -105,15 +107,16 @@
</intent-filter>
</activity>
<activity android:name="keepass2android.softkeyboard.InputLanguageSelection"
android:exported="true">
<!-- android:label="@string/language_selection_title" TODO -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</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">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -275,6 +278,7 @@ 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.FOREGROUND_SERVICE" />
<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.MANAGE_ACCOUNTS" android:maxSdkVersion="22" />
<uses-permission android:name="keepass2android.keepass2android.permission.KP2aInternalFileBrowsing" />

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="222"
android:versionName="1.13-r0"
android:versionCode="200"
android:versionName="1.11-r0"
package="keepass2android.keepass2android_nonet"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
@@ -11,9 +11,6 @@
<!-- Specific intents and packages we query for (required since Android 11) -->
<package android:name="keepass2android.plugin.keyboardswap2" />
<package android:name="keepass2android.AncientIconSet" />
<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>
@@ -42,13 +39,13 @@
<action android:name="android.intent.action.VIEW" />
</intent>
</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_desc3" android:icon="@drawable/ic_launcher_offline" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_nonet.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
<application android:label="keepass2android"
<application
android:label="keepass2android"
android:icon="@mipmap/ic_launcher_offline"
android:roundIcon="@mipmap/ic_launcher_offline_round"
android:networkSecurityConfig="@xml/network_security_config"
>
@@ -81,6 +78,7 @@
</intent-filter>
</activity>
<activity android:name="keepass2android.softkeyboard.InputLanguageSelection"
android:label="@string/language_selection_title"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -88,6 +86,7 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</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">
<intent-filter android:label="@string/app_name">
@@ -219,6 +218,15 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdb" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="https"
android:host="my.yubico.com"
android:pathPrefix="/neo"/>
</intent-filter>
<intent-filter android:label="@string/kp2a_findUrl">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
@@ -246,18 +254,17 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
</application>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<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-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_DATA_SYNC" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- Samsung Pass permission -->
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalFileBrowsing" />
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />
</manifest>

View File

@@ -66,6 +66,7 @@ using Exception = System.Exception;
using String = System.String;
using Toolbar = AndroidX.AppCompat.Widget.Toolbar;
using AndroidX.Core.Content;
using Google.Android.Material.Snackbar;
namespace keepass2android
{
@@ -131,7 +132,6 @@ namespace keepass2android
ISharedPreferences _prefs;
private bool _starting;
private bool _resumeCompleted;
private OtpInfo _otpInfo;
private IOConnectionInfo _otpAuxIoc;
private ChallengeInfo _chalInfo;
@@ -222,6 +222,7 @@ namespace keepass2android
//StackBaseActivity will launch the next activity
Intent data = new Intent();
data.PutExtra("ioc", IOConnectionInfo.SerializeToString(_ioConnection));
data.PutExtra("requiresSubsequentSync", _lastLoadOperation?.RequiresSubsequentSync == true);
SetResult(Result.Ok, data);
@@ -421,12 +422,6 @@ namespace keepass2android
{
var iocAux = GetDefaultAuxLocation();
LoadFile(iocAux);
if (Activity._chalInfo == null)
{
throw new Java.Lang.Exception("Failed to load challenge aux file");
}
}
catch (Exception e)
{
@@ -807,6 +802,8 @@ namespace keepass2android
_password = i.GetStringExtra(KeyPassword) ?? "";
if (!KeyProviderTypes.Any())
{
SetKeyProviderFromString(LoadKeyProviderStringForIoc(_ioConnection.Path));
}
@@ -1423,8 +1420,6 @@ namespace keepass2android
if (cbQuickUnlock == null)
throw new NullPointerException("cpQuickUnlock");
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase =
(((KeyguardManager)GetSystemService(Context.KeyguardService)!)!).IsDeviceSecure;
if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline))
{
@@ -1446,14 +1441,21 @@ namespace keepass2android
MakePasswordMaskedOrVisible();
Handler handler = new Handler();
OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
LoadDb task = (KeyProviderTypes.Contains(KeyProviders.Otp))
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
LoadDb loadOperation = (KeyProviderTypes.Contains(KeyProviders.Otp))
? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(),
onFinish, this, true, _makeCurrent)
: new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onFinish,true, _makeCurrent);
onOperationFinishedHandler, this, true, _makeCurrent)
: new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onOperationFinishedHandler,true, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new ProgressTask(App.Kp2a, this, task).Run();
_lastLoadOperation = loadOperation;
//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)
{
@@ -1578,7 +1580,7 @@ namespace keepass2android
}
private bool hasRequestedKeyboardActivation = false;
private LoadDb _lastLoadOperation;
protected override void OnStart()
{
@@ -1699,20 +1701,15 @@ namespace keepass2android
//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)
{
ResetState();
GetIocFromLaunchIntent(intent);
InitializeAfterSetIoc();
OnStart();
}
}
}
@@ -1766,17 +1763,10 @@ namespace keepass2android
cbOfflineMode.Checked =
App.Kp2a
.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);
var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
{
offlineModeContainer.Visibility = ViewStates.Visible;
}
else
{
offlineModeContainer.Visibility = ViewStates.Gone;
App.Kp2a.OfflineMode = false;
}
CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background)!;
cbSyncInBackground.Checked = App.Kp2a.SyncInBackgroundPreference;
UpdateInternalCacheCheckboxesVisibility();
@@ -1831,6 +1821,43 @@ namespace keepass2android
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!)
@@ -1867,12 +1894,13 @@ namespace keepass2android
// 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);
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true;
LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onFinish, false, _makeCurrent);
LoadDb loadOperation = new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onOperationFinishedHandler, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new ProgressTask(App.Kp2a, this, task).Run();
_lastLoadOperation = loadOperation;
OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
}
@@ -1894,8 +1922,6 @@ namespace keepass2android
keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly);
}, 50);
}
_resumeCompleted = true;
}
private void TryGetOtpFromClipboard()
@@ -2018,6 +2044,34 @@ namespace keepass2android
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 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) {
@@ -2088,11 +2142,11 @@ namespace keepass2android
Finish();
}
private class AfterLoad : OnFinish {
private class AfterLoad : OnOperationFinishedHandler {
readonly PasswordActivity _act;
private readonly IOConnectionInfo _ioConnection;
public AfterLoad(Handler handler, PasswordActivity act, IOConnectionInfo ioConnection):base(act, handler)
public AfterLoad(Handler handler, PasswordActivity act, IOConnectionInfo ioConnection):base(App.Kp2a, handler)
{
_act = act;
_ioConnection = ioConnection;
@@ -2199,7 +2253,7 @@ namespace keepass2android
if (!Success)
_act.InitFingerprintUnlock();
_act._lastLoadOperation = null;
_act._performingLoad = false;
}
@@ -2233,7 +2287,7 @@ namespace keepass2android
private readonly PasswordActivity _act;
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)
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)
{
_act = act;
}

View File

@@ -25,7 +25,6 @@ using Android.Widget;
using Android.Content.PM;
using KeePassLib.Keys;
using Android.Preferences;
using Android.Provider;
using Android.Runtime;
using Android.Views.InputMethods;
@@ -163,29 +162,6 @@ namespace keepass2android
if (bundle != null)
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;
}
}
@@ -363,9 +339,9 @@ namespace keepass2android
if (PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.SyncAfterQuickUnlock_key), false))
{
new SyncUtil(this).SynchronizeDatabase(Finish);
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
}
else
Finish();

View File

@@ -79,12 +79,6 @@ android:paddingRight="16dp"
android:paddingTop="16dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/QuickUnlockForm">
<TextView
android:id="@+id/QuickUnlock_label"
android:text="@string/QuickUnlock_label"
@@ -94,6 +88,11 @@ android:paddingRight="16dp"
android:textSize="14sp"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:inputType="textPassword"
android:layout_width="wrap_content"
@@ -122,60 +121,6 @@ android:paddingRight="16dp"
</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
android:id="@+id/spacing"

View File

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

View File

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

View File

@@ -4,18 +4,35 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<keepass2android.views.BackgroundOperationContainer
android:visibility="gone"
android:id="@+id/background_ops_container"
android:layout_below="@id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ScrollView
android:id="@+id/entry_scroll"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
android:background="?android:attr/colorBackground"
android:scrollbarStyle="insideOverlay">
<keepass2android.view.EntryContentsView
android:id="@+id/entry_contents"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
</ScrollView>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/entry_edit"
android:layout_width="wrap_content"

View File

@@ -355,12 +355,21 @@
android:layout_above="@id/bottom_bar"
android:background="#b8b8b8" />
<keepass2android.views.BackgroundOperationContainer
android:visibility="gone"
android:id="@+id/background_ops_container"
android:layout_below="@id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/divider2"
android:layout_below="@id/top"
android:layout_below="@id/background_ops_container"
android:fitsSystemWindows="true">
<fragment
android:name="keepass2android.GroupListFragment"

View File

@@ -318,6 +318,30 @@
android:text="@string/help_quickunlock"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/sync_in_background_container"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/sync_in_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/SyncOfflineCacheInBackground_title" />
<keepass2android.views.Kp2aShortHelpView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance_Help_Dense"
app:help_text="@string/SyncOfflineCacheInBackground_summary"
app:title_text="@string/SyncOfflineCacheInBackground_title"
android:text="@string/SyncOfflineCacheInBackground_summary"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/work_offline_container"
android:orientation="horizontal"
@@ -328,7 +352,7 @@
android:id="@+id/work_offline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/UseOfflineMode" />
<keepass2android.views.Kp2aShortHelpView
android:layout_width="wrap_content"
@@ -341,6 +365,7 @@
/>
</LinearLayout>
<Button
android:id="@+id/kill_app"
android:text="@string/kill_app_label"

View File

@@ -1169,8 +1169,4 @@ První veřejné vydání
<string name="kp2a_switch_on_sendgodone_summary">Přepnout zpět při stisknutí tlačítka odeslat/pokračovat/ukončit</string>
<string name="qr_scanning_error_no_google_play_services">Skenování QR kódu vyžaduje služby Google Play. Nainstalujte nebo aktualizujte služby Google Play ve svém zařízení.</string>
<string name="english_ime_settings">Nastavení klávesnice Keepass2Android</string>
<string name="autoswitch_enabled_but_not_setup">Poznámka: Máte povoleno Aplikace - Nastavení - Přístup k heslu - Přepínání klávesnice - Automatické přepínání klávesnice, ale nezdá se, že je správně nakonfigurováno.</string>
<string name="switch_keyboard_for_totp_enabled">Poznámka: Máte povoleno Aplikace - Přístup k heslu - Služba automatického vyplňování - Automatické vyplňování pro položky TOTP. To může způsobit, že se toto okno zobrazí, když otevřete položku s TOTP.</string>
<string name="switch_keyboard_inside_kp2a_enabled">Poznámka: V aplikaci Keepass2Android jste povolili možnost Aplikace - Zabezpečení - Použít vestavěnou klávesnici. To může způsobit, že se toto okno zobrazí při otevření aplikace nebo úpravě položky.</string>
<string name="switch_keyboard_on_search_enabled">Poznámka: Máte povoleno Aplikace - Zabezpečení - Přístup k heslu - Přepínání klávesnice - Přepínání klávesnice. To může způsobit, že se toto okno zobrazí při vyhledávání položky z prohlížeče.</string>
</resources>

View File

@@ -164,7 +164,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="invalid_db_sig">Datenbank-Format wurde nicht erkannt.</string>
<string name="keyfile_does_not_exist">Schlüssel-Datei existiert nicht.</string>
<string name="no_keyfile_selected">Keine Schlüsseldatei ausgewählt.</string>
<string name="keyfile_is_empty">Schlüsseldatei ist leer.</string>
<string name="keyfile_is_empty">Schlüssel-Datei ist leer.</string>
<string name="length">Länge</string>
<string name="list_size_title">Größe der Gruppenliste</string>
<string name="list_size_summary">Schriftgröße in der Gruppenliste</string>
@@ -176,8 +176,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="masktotp_title">TOTP-Feld verdecken</string>
<string name="masktotp_summary">TOTP-Feld standardmäßig ausblenden</string>
<string name="NoAutofillDisabling_title">Keine Option, um Autofill zu deaktivieren</string>
<string name="NoAutofillDisabling_summary">Wenn aktiviert, wird die App die Option zum Abschalten von Autofill für bestimmte Einträge nicht angezeigt.
</string>
<string name="NoAutofillDisabling_summary">Wenn aktiviert, wird die App die Option zum Abschalten von Autofill für bestimmte Einträge nicht anzeigen. </string>
<string name="menu_about">Über</string>
<string name="menu_change_key">Hauptschlüssel ändern</string>
<string name="menu_copy_pass">Passwort kopieren</string>
@@ -291,7 +290,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="please_note">Bitte beachten</string>
<string name="contributors">Mitwirkende</string>
<string name="regular_expression">Regulärer Ausdruck</string>
<string name="AlwaysMergeOnConflict_title">Bei Konflikt immer zusammenführen</string>
<string name="AlwaysMergeOnConflict_title">Bei Konflikt immer vereinigen</string>
<string name="AlwaysMergeOnConflict_summary">Die lokalen Änderungen immer mit den entfernten Änderungen vereinigen, wenn Keepass2Android erkennt, dass die entfernte Datei verändert wurde.</string>
<string name="TanExpiresOnUse_title">TAN verfällt bei Verwendung</string>
<string name="TanExpiresOnUse_summary">TAN-Einträge als abgelaufen markieren, wenn sie verwendet werden</string>
@@ -333,7 +332,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="QuickUnlockLength_summary">Maximale Anzahl von Zeichen, die als QuickUnlock-Passwort verwendet werden.</string>
<string name="QuickUnlockHideLength_title">Länge des QuickUnlock-Codes verstecken</string>
<string name="QuickUnlockHideLength_summary">Wenn aktiviert, wird die Länge des QuickUnlock-Codes nicht auf dem QuickUnlock-Bildschirm angezeigt.</string>
<string name="QuickUnlockKeyFromDatabase_title">QuickUnlock-Schlüssel aus Datenbankeintrag</string>
<string name="QuickUnlockKeyFromDatabase_title">QuickUnlock-Taste aus dem Datenbankeintrag.</string>
<string name="QuickUnlockKeyFromDatabase_summary">Wenn die aktive Datenbank einen Eintrag mit dem Titel „QuickUnlock“ in der Root-Gruppe enthält, wird das Passwort dieses Eintrags als QuickUnlock-Code verwendet.</string>
<string name="QuickUnlock_fail">QuickUnlock fehlgeschlagen: falsches Passwort!</string>
<string name="SaveAttachmentDialog_title">Anhang speichern</string>
@@ -379,7 +378,7 @@ Der Android Robot wird genutzt und wurde modifiziert basierend auf Arbeiten, die
<string name="ActivateSearchViewOnStart_summary">Aktiviert das Suchfeld in der Gruppenansicht nach dem Entsperren oder wenn ein Eintrag gesucht wird.</string>
<string name="NoDonateOption_title">Spenden-Option ausblenden</string>
<string name="NoDonateOption_summary">Diese Einstellung ist ein Dankeschön an alle, die gespendet haben. Sie steht erst nach einer gewissen Zeit der Benutzung von Keepass2Android zur Verfügung.</string>
<string name="NoDonateOption_question">Ohne Spenden würde diese App nicht existieren und ständig verbessert werden! Wenn du noch nicht gespendet hast, erwäge bitte, dies jetzt zu tun</string>
<string name="NoDonateOption_question">Ohne Spenden gäbe es diese App nicht und sie könnte nicht ständig verbessert werden! Wenn Sie noch nicht gespendet haben, sollten Sie dies jetzt in Betracht ziehen.</string>
<string name="NoDonationReminder_title">Nie um eine Spende bitten</string>
<string name="NoDonationReminder_summary">Ich werde keinen Cent spenden oder habe bereits gespendet. Bitte niemals um eine Spende, nicht einmal am Geburtstag des Autors.</string>
<string name="UseOfflineCache_title">Datenbank-Caching</string>
@@ -718,12 +717,12 @@ Anbei einige Tipps, die bei der Diagnose des Problems helfen können:\n
<string name="CloseDbAfterFailedAttempts">Datenbank nach drei fehlgeschlagenen biometrischen Entsperrversuchen schließen.</string>
<string name="WarnFingerprintInvalidated">Achtung! Die biometrische Authentifizierung kann von Android ungültig gemacht werden, z. B. nach dem Hinzufügen eines neuen Fingerabdrucks in den Geräteeinstellungen. Bitte sicherstellen, dass jederzeit klar ist, wie mit dem eigenen Hauptpasswort entsperrt werden kann!</string>
<string-array name="ChangeLog_1_12">
<item>Von Xamarin Android auf .NET 8 upgegradet</item>
<item>Upgraded from Xamarin Android to .net 8</item>
<item>Upgrade auf Target-SDK 34</item>
<item>Auf MUI 3 upgegradet</item>
<item>Verbessertes automatisches Ausfüllen damit Compose-Apps funktionieren</item>
<item>Hostnamen-vergleiche im automatischen Ausfüllen und der Suche gefixt</item>
<item>Probleme mit dem Passwort Generator behoben</item>
<item>Upgraded to Material 3 user interface</item>
<item>Improve autofill to work with Compose apps</item>
<item>Fix hostname matching in autofill and search</item>
<item>Fix issue with password generator</item>
</string-array>
<string-array name="ChangeLog_1_12_net">
<item>OneDrive SDK auf Version 5.68 aktualisiert</item>
@@ -760,7 +759,7 @@ Anbei einige Tipps, die bei der Diagnose des Problems helfen können:\n
<string-array name="ChangeLog_1_09d">
<item>Unterstützung für das Ansehen, Entfernen und Wiederherstellen von Eintragssicherungen hinzugefügt</item>
<item>Unterstützung für MEGA-Cloudspeicher hinzugefügt </item>
<item>Unterstützung für Google Drive mit eingeschränktem Zugriff hinzugefügt</item>
<item>Unterstützung für Google Drive mit eingeschränktem Anwendungsbereich hinzugefügt</item>
</string-array>
<string-array name="ChangeLog_1_09c">
<item>Google-Drive-Authentifizierung erneut implementiert, Google-Drive-Unterstützung wieder aktiviert </item>
@@ -1146,7 +1145,7 @@ Erstes öffentliches Release</string>
<string name="masterkey_infotext_head">Kennst du dein Master-Passwort?</string>
<string name="masterkey_infotext_main">Bitte beachte, dass du deine Datenbank ohne den Hauptschlüssel nicht öffnen kannst. Es gibt keine Möglichkeit, das Hauptpasswort „zurückzusetzen“.</string>
<string name="masterkey_infotext_fingerprint_note">Bitte auch bedenken, dass das Biometrische Entsperren über das Speichern des Hauptschlüssels im sicheren Speicher von Android funktioniert. Dieser Speicher kann von Android jederzeit gelöscht werden, z. B. wenn ein neuer Fingerabdruck in den Systemeinstellungen hinzugefügt wird. Daher sich nicht auf das biometrische Entsperren verlassen, sondern sich bitte das eigene Hauptpasswort merken!</string>
<string name="backup_infotext_head">Ist deine Datenbank gesichert?</string>
<string name="backup_infotext_head">Gibt es eine Sicherung der Datenbank?</string>
<string name="backup_infotext_main">Keepass2Android speichert die Passwörter in einer Datei an einem frei wählbarem Speicherort.Ist sichergestellt, dass die Datei auch dann noch verfügbar ist, wenn das Telefon verloren geht oder gestohlen wird, oder wenn die Datei zerstört oder gelöscht wird? Bitte sicherstellen, dass immer eine aktuelle Kopie an einem sichern Ort aufbewahrt wird!</string>
<string name="backup_infotext_note">Um jetzt eine Sicherung zu erstellen, nach %1$s &gt; %2$s &gt; %3$s gehen.</string>
<string name="emergency_infotext_head">Hast du für Notfälle vorgesorgt?</string>
@@ -1163,5 +1162,4 @@ Erstes öffentliches Release</string>
<string name="kp2a_switch_on_sendgodone_summary">Beim Drücken von Senden/Los/Fertig zurückschalten</string>
<string name="qr_scanning_error_no_google_play_services">QR-Code-Scannen erfordert Google Play-Dienste. Bitte installiere oder aktualisiere Google Play-Dienste auf deinem Gerät.</string>
<string name="english_ime_settings">Android-Tastatureinstellungen</string>
<string name="autoswitch_enabled_but_not_setup">Notiz: Du hast Einstellungen - Anwendung - Passwortzugriff - Wechsel der Eingabemethode - Auto-Umschalten der Tastatur eingeschaltet, aber sieht aus als würde es nicht funktionieren</string>
</resources>

View File

@@ -719,14 +719,6 @@
<string name="EntryChannel_desc">Notificación para simplificar el acceso a la entrada seleccionada actualmente.</string>
<string name="CloseDbAfterFailedAttempts">Cierre la base de datos después de tres intentos fallidos de desbloqueo biométrico.</string>
<string name="WarnFingerprintInvalidated">¡Atención! La autenticación biométrica puede ser invalidada por Android, p. ej. después de añadir una nueva huella dactilar en los ajustes de su dispositivo. ¡Esté seguro de conocer siempre cómo desbloquear con su contraseña maestra!</string>
<string-array name="ChangeLog_1_12">
<item>Actualizado desde Xamarin Android a .net 8</item>
<item>Upgraded to Target SDK 34</item>
<item>Upgraded to Material 3 user interface</item>
<item>Improve autofill to work with Compose apps</item>
<item>Fix hostname matching in autofill and search</item>
<item>Fix issue with password generator</item>
</string-array>
<string-array name="ChangeLog_1_11">
<item>Añadidos botones de acción flotante para la búsqueda y vista general de TOTP (si las entradas TOTP están presentes).</item>
<item>Se ha mejorado la visualización de los campos TOTP añadiendo un indicador de tiempo de espera y mostrándolo de forma más destacada.</item>
@@ -1186,5 +1178,4 @@
<string name="kp2a_switch_on_sendgodone">Volver cuando termine</string>
<string name="kp2a_switch_on_sendgodone_summary">Volver cuando se presione enviar/ir/finalizado</string>
<string name="english_ime_settings">Ajustes del teclado de Keepass2Android</string>
<string name="switch_keyboard_on_search_enabled">Nota: Ha habilitado la aplicación - Seguridad - Acceso a la contraseña - Interruptor de teclado - Cambiar teclado. Esto puede causar que esta ventana se muestre cuando busque una entrada desde el navegador.</string>
</resources>

View File

@@ -546,7 +546,6 @@
<string name="filestoragename_dropboxKP2A">Dropbox (dossier KP2A)</string>
<string name="filestoragehelp_dropboxKP2A">Si vous ne voulez pas donner l\'accès KP2A à votre espace Dropbox complet, vous pouvez sélectionner cette option. Seul l\'accès au dossier Apps/Keepass2Android sera demandé. Ceci est particulièrement adapté lorsque vous créez une nouvelle base de données. Si vous avez déjà une base de données, cliquez sur cette option pour créer le dossier, puis placez votre fichier dans le dossier (à partir de votre PC) et puis sélectionnez à nouveau cette option pour ouvrir le fichier.</string>
<string name="filestoragename_gdrive">Google Drive</string>
<string name="filestoragehelp_gdrive">Attention : Google restreint l\'accès à Google Drive depuis les applications pour un nombre grandissant d\'utilisateurs. Si l\'intégration de Google Drive ne fonctionne pas, utilisez plutôt le sélecteur de fichiers système et sélectionnez Google Drive !</string>
<string name="filestoragename_gdriveKP2A">Google Drive (fichiers KP2A)</string>
<string name="filestoragehelp_gdriveKP2A">Si vous ne voulez pas donner à KP2A un accès complet à Google Drive, vous pouvez sélectionner cette option. Notez que vous devez d\'abord créer un fichier de base de données, les fichiers existants ne sont pas visibles pour l\'application. Choisissez cette option dans l\'écran Créer une base de données ou, si vous avez déjà ouvert une base de données, en exportant la base de données en choisissant cette option.</string>
<string name="filestoragename_pcloud">PCloud (dossier KP2A)</string>
@@ -724,15 +723,9 @@
<item>Mise à niveau de Xamarin Android vers. net 8</item>
<item>Mise à niveau vers Target SDK 34</item>
<item>Mise à niveau vers l\'interface utilisateur Material 3</item>
<item>Amélioration de la saisie automatique pour fonctionner avec les applications Compose</item>
<item>Improve autofill to work with Compose apps</item>
<item>Fix hostname matching in autofill and search</item>
<item>Corriger le problème avec le générateur de mot de passe</item>
</string-array>
<string-array name="ChangeLog_1_12_net">
<item>Mise à jour de OneDrive SDK vers la version 5.68</item>
<item>Mise à jour du SDK Dropbox vers la version 7.0.0</item>
<item>Mise à jour Gradle, NewtonsoftJson, FluentFTP, MegaApiClient et okhttp</item>
<item>Correction de bugs dans la sélection de fichiers WebDav</item>
<item>Fix issue with password generator</item>
</string-array>
<string-array name="ChangeLog_1_11">
<item>Ajout de boutons d\'action flottants pour la recherche et l\'aperçu TOTP (si des entrées TOTP sont présentes).</item>
@@ -828,89 +821,6 @@
* Nouvelle implémentation pour OneDrive : inclut la prise en charge de OneDrive Entreprise, des fichiers partagés, des périmètres d\'accès sélectionnables, des comptes multiples et des corrections de problèmes d\'accès en mode hors-ligne\n
* Corrections d\'anomalies
</string>
<string name="ChangeLog_1_07"> Version 1.07\n
* Corrige les crashs sur les Samsung avec Android 9\n
* Permet d\'ouvrir plus d\'une base de données, compatible avec KeeAutoExec\n
* SFTP : permet l\'authentification avec clé publique, vérifie si la clé de l\'hôte a changé\n
* Introduction du support pCloud - merci à gilbsgilbs !\n
* Mise en place explicite du support Nextcloud\n
* Améliore l\'enregistrement et la mise à jour des pièces jointes des entrées\n
* Plus d\'options pour adapter le comportement aux préférences personnelles\n
* SSL : acceptation et confiance avec les certificats utilisateur\n
* Améliore la saisie automatique (fonctionne maintenant avec Firefox, permet de réduire les popups)\n
* Résolution de bugs\n
</string>
<string name="ChangeLog_1_06">Version 1.06\n
* Changement vers ykDroid au lieu de YubiChallenge comme application pour le défi-réponse de Yubikey.\n
* implémentation du support de défi-réponse compatible KeepassXC. Note : Le format de base de données doit être KDBX4 !\n
* Blocage du chargement de fichiers supprimés depuis Google Drive\n
* Changement d\'implémentation TLS pour FTPS, ajout d\'un contournement du bug JSch avec les serveurs compatibles avec gssapi-with-mic\n
* Correction de bugs\n </string>
<string name="ChangeLog_1_05"> Version 1.05\n * Utilisation des canaux de notification Android 8, ce qui permet la configuration via les paramètres système\n * Affichage de licône entrée dans la notification\n * Utilisation d\'icônes adaptatifs pour Android 8, utilisez l\'icône arrondi du lanceur pour Android 7\n * Permet dactiver la recherche au déverrouillage (voir réglages) \n * Modification de comment les fichiers sont écrits en utilisant le framework Storage Access, corrections de problèmes sur les mises à jour des fichiers ouvert sur Google Drive avec le sélecteur du système fichier\n * Ajout de textes dinformations afin déviter certaines incompréhensions commune \n * Création de sauvegardes locales pour les bases ouvertes avec succès pour réduire le risque de perte de donnée\n * Mise à jour de JSch pour supporté les derniers chiffrements SSH\n * Permettre la modification des paramètres de connexion, par exemple lorsque le mot de passe WebDav a changé\n * Ajout du support pour mot de passe statique Yubikey Neo\n * Permettre de désactiver les suggestions automatiques\n * Correction des fuites de données vers logcat\n * Corrections d\'anomalies\n
</string>
<string name="ChangeLog_1_04b"> Version 1.04b\n * Évite les plantages lorsque lutilisateur essaie dactiver la fonctionnalité de remplissage automatique sur les appareils Huawei.\n
</string>
<string name="ChangeLog_1_04">Version 1.04
* Ajout du service de saisie automatique pour Android 8.0 et plus.
* Mise à niveau des librairies, des outils de build et de la version du sdk cible
</string>
<string name="ChangeLog_1_03">Version 1.03
* Suppression du service d\'accessibilité pour le remplissage automatique tel que demandé par Google. Voir les paramètres daccès mot de passe pour trouver un plugin reproduisant la fonctionnalité précédente.
* Ajout d\'applications tierces comme option de stockage
* Visionneuse intégrée pour le voir des images jointes sans les transférer sur dautres applications
* OkHttp mis à jour pour résoudre certains problèmes de connexion
* Support des entrées de KeeTrayTOTP, supporte désormais les entrées Steam</string>
<string name="ChangeLog_1_02"> Version 1.02\n
* Quelques améliorations liées à la sécurité. Merci beaucoup pour le rapport jean-baptiste.cayrou@thalesgroup.com et vincent.fargues@thalesgroup.com et aussi pour votre collaboration !\n
* Support de KeyboardSwapPlugin (voir les paramètres d\'accès aux mots de passe) : permet de changer de méthode de saisie automatiquement sur les appareils sans « root ». Merci à Mishaal Rahman d\'XDA-Developers de l\'avoir rendu possible.\n
* Correction pour les services daccessibilité avec les versions récentes de Chrome\n
* Correction pour les nettoyages non nécessaires des données d\'empreintes digitales\n
* Correction de plantages mineurs\n
* Mise à jour du kit de développement Dropbox pour s\'assurer de la compatibilité future\n
* Suppression des rapports d\'erreur en utilisant Xamarin Insights\n
* Mise à jour des outils de compilation\n
</string>
<string name="ChangeLog_1_01g"> Version 1. 1-g\n
* Correction pour un plantage lorsque vous essayez de travailler hors ligne\n
* Correction pour un codage incorrect des identifiants FTP(S)\n
* Correction pour les plantages lors de l\'utilisation de OneDrive et des anciennes versions d\'Android\n
* Affiche les temps en temps local dans l\'écran d\'entrée\n
</string>
<string name="ChangeLog_1_01d">Version 1.01-d\n * Correction pour le listing des fichiers OneDrive\n * Possibilité dignorer les erreurs de certificat lorsque la vérification du nom de lhôte échoue (non recommandé pour une utilisation en production) \n * Correction pour QuickUnlock qui parfois ne fonctionnait malgré la saisie correcte du code\n </string>
<string name="ChangeLog_0_9_8c">Version 0.9.8c\n
* Correctif pour la vulnérabilité SSL dans Microsoft Live SDK (utilisé lorsque vous accédez à des fichiers via OneDrive)\n
* Bug fix : la version précédente contenait deux méthodes de saisie (l\'une des deux plante)\n </string>
<string name="ChangeLog_1_01">Version 1.01\n * Ajout du support pour le nouveau format KDBX-4 (compatible avec Keepass 2.35) y compris la dérivation de clés Argon2 et ChaCha20 cryptage.\n * Nouvelle implémentation du stockage WebDav, il est maintenant possible de parcourir les fichiers et le stockage prend en charge un chiffrement moderne.\n * Nouvelle implémentation du stockage FTP qui permet maintenant de parcourir les fichiers et de prendre en charge le cryptage (FTPS).\n * Mise à jour du SDK OneDrive (le SDK Live précédemment utilisé nest plus mis à jour).\n * Mise à jour du SDK Dropbox vers la version 2 (la version 1 n\'est plus supportée).\n * Ajout du support pour OwnCloud.\n *Demande dautorisation de stockage avant douvrir les fichiers locaux. </string>
<string name="ChangeLog_1_0_0e">Version 1.0.0e\n
* Règle un problème avec le scaneur d\'empreintes digitales sur les anciens appareils Samsung tournant avec Android 6.\n
* Ajout d\'un support natif pour les processeurs x86.\n
* Permet d\'afficher ou cacher les caractères dans les champs texte de saisie si le scanner d\'empreinte digital a été activé. \n
* Construction de mise à jour système </string>
<string name="ChangeLog_1_0_0">Version 1.0.0\n * Déverrouiller par empreinte digitale (nécessite Android 6.0+ ou un appareil Samsung)\n * Ajout du service de saisie automatique (nécessite Android 5.0+)\n * Ajout du support pour l\'entrée templates\n * Ajout d\'un mode \"hors connexion\"\n * Possibilité de copier les entrées\n * Mode semi-automatique pour les champs noms\n * Possibilité de supprimer des éléments de liste des fichiers récents\n * Demande des autorisations à l\'exécution pour Android 6.0\n * Corrections de bugs (dans le clavier intégré, lorsque vous sélectionnez les icônes)\n * Ajout d\'une option pour envoyer des rapports d\'erreur\n * Ajouts de plusieurs messages d\'aide\n </string>
<string name="ChangeLog_0_9_9">Version 0.9.9\n
* Refonte complète de l\'interface utilisateur. Merci beaucoup à Stefano Pignataro (http://www.spstudio.at) pour son aide!\n
* Permet l\'ajout d\'icônes personnalisées\n
* Support pour l\'affichage fractionné sur les appareils Samsung\n
* Nombre de rotation par défaut pour le chiffrement de nouvelle base de données\n
* Vérification des clés dupliquées des champs supplémentaires pour éviter la perte de données\n
</string>
<string name="ChangeLog_0_9_9c"> Version 0.9.9c
* Retour du thème sombre
* Vous pouvez installer d\'autres pack d\'icones (Icones Windows ancien sont disponibles sur le Play Store)
* Ajout d\'une confirmation lors d\'une suppression d\'éléments sans passer par la corbeille
* Correction de bugs (mauvais affichage de l\'encodage secret OTP, mauvaise icone de l\'application dans certains endroits)
</string>
<string name="ChangeLog_0_9_8b">Version 0.9.8b\n
* corrections de bugs (Sauvegarde a échoué pour certaines bases, exporter vers le périphérique local ne fonctionne pas, sélectionner certaines options de préférence crash l\'app)\n</string>
<string name="ChangeLog_0_9_8">Version 0.9.8\n
* Support du Storage Access Framework (permet l\'écriture sur carte SD et Google Drive dans KP2A Offline)\n
* Essai de détecter les erreurs de saisie dans les URL WebDAV (répertoire au lieu du fichier)\n
* Police de mot de passe modifié\n
* Autorise le changement de compte Dropbox\n
* correction de bug: maintenant se souvient du mot de passe OTP\n</string>
<string name="ChangeLog_0_9_7b">Version 0.9.7b\n
* traductions actualisées\n
* corrections de bugs : polices de mot de passe était manquant dans 0.9.7, trie par nom ne trie pas les groupes\n</string>
<string name="ChangeLog_keptDonate">Possibilité élargie de faire un don d\'une bière ou autre chose</string>
<string-array name="clipboard_timeout_options">
<item>30 secondes</item>
@@ -1002,10 +912,5 @@
<string name="AutofillWarning_Intro">Vous êtes sur le point d\'insérer des identifiants pour le domaine \"%1$s\" dans l\'application \"%2$s\".</string>
<string name="AutofillWarning_FillDomainInUntrustedApp">Si vous faites confiance à \"%2$s\" pour appartenir à \"%1$s\" ou que vous faites confiance à l\'application \"%2$s\" pour ne pas abuser des identifiants (ex. parce que c\'est une application de navigateur de confiance), il est possible de continuer. Si ce n\'est pas le cas, veuillez annuler.</string>
<string name="AutofillWarning_trustAsBrowser">Accepter toujours dans \"%1$s\"</string>
<string name="qr_scanning_error_no_google_play_services">Le scan de QR Code requiert les Services Google Play. Veuillez installer ou mettre à jour les Services Google Play sur votre périphérique.</string>
<string name="english_ime_settings">Paramètres du clavier Keepass2Android</string>
<string name="autoswitch_enabled_but_not_setup">Note : Vous avez activé le changement automatique de clavier, mais il ne semble pas être correctement configuré (Paramètres - Appli - Accès aux mots de passe - Changement de clavier).</string>
<string name="switch_keyboard_for_totp_enabled">Note : Vous avez activé le remplissage automatique des entrées TOTP. Cela peut entrainer l\'apparition de cette fenêtre lorsque vous ouvrez une entrée avec un TOTP (Paramètres - Appli - Accès aux mots de passe - Service de saisie automatique).</string>
<string name="switch_keyboard_inside_kp2a_enabled">Note : Vous avez activé le clavier intégré dans Keepass2Android. Cela peut faire apparaitre cette fenêtre quand vous ouvrez l\'application, ou que vous éditez une entrée (Paramètres - Appli - Sécurité).</string>
<string name="switch_keyboard_on_search_enabled">Note : Vous avez activé le changement de clavier. Cela peut faire apparaitre cette fenêtre lorsque vous recherchez une entrée depuis le navigateur (Paramètres - Appli - Accès aux mots de passe - Changement de clavier).</string>
</resources>

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