Compare commits
98 Commits
v1.12-r2
...
374-improv
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3c9c8610d | ||
![]() |
9c2d50c7b0 | ||
![]() |
0cd9df7415 | ||
![]() |
a4a3112dc6 | ||
![]() |
6351e1f3d0 | ||
![]() |
2959d8cbcc | ||
![]() |
c98680347a | ||
![]() |
35367bb28b | ||
![]() |
8a1890bc10 | ||
![]() |
bf4035fcfe | ||
![]() |
d3dfbaab4b | ||
![]() |
227074efb6 | ||
![]() |
69f79c1b20 | ||
![]() |
000d1254ec | ||
![]() |
324fc1f2ee | ||
![]() |
52121c6a85 | ||
![]() |
c6f494ac33 | ||
![]() |
400e171bc5 | ||
![]() |
41e6e67e87 | ||
![]() |
8277283ebc | ||
![]() |
71806178d0 | ||
![]() |
aa2e4b856d | ||
![]() |
cfb185b53d | ||
![]() |
c3b6612591 | ||
![]() |
fefcf8f30e | ||
![]() |
e95cc84a15 | ||
![]() |
c0ed185612 | ||
![]() |
61c871f782 | ||
![]() |
e5d28f0979 | ||
![]() |
0e581a66c5 | ||
![]() |
ceb31c54b1 | ||
![]() |
42d8be593e | ||
![]() |
313adb6c3e | ||
![]() |
668ba4cdee | ||
![]() |
a36bfa7ff5 | ||
![]() |
26c37bcd2a | ||
![]() |
1980f05a7c | ||
![]() |
dbf10ba9fb | ||
![]() |
4be18d8373 | ||
![]() |
831b290d81 | ||
![]() |
9d4c15f7bc | ||
![]() |
4c4afa792d | ||
![]() |
8e256ac94d | ||
![]() |
65ff09f866 | ||
![]() |
8e9c2824cf | ||
![]() |
92b8ff5c8d | ||
![]() |
223c3bfb8e | ||
![]() |
b4e03a8374 | ||
![]() |
fb2df35d37 | ||
![]() |
345dad5d04 | ||
![]() |
50d6598b02 | ||
![]() |
90f04b76f4 | ||
![]() |
8b4314c394 | ||
![]() |
17241bc422 | ||
![]() |
c4a73bf107 | ||
![]() |
e76f3999b6 | ||
![]() |
0567bfe645 | ||
![]() |
6043bdbc48 | ||
![]() |
4ba40ba24f | ||
![]() |
e2711b709d | ||
![]() |
4764b15e75 | ||
![]() |
1b389ef12e | ||
![]() |
b32c2dbc7e | ||
![]() |
f06937dab3 | ||
![]() |
14efce62ff | ||
![]() |
3c8b530e2e | ||
![]() |
9939e07b7d | ||
![]() |
ecf416febc | ||
![]() |
f949e6e389 | ||
![]() |
15bf08f5e6 | ||
![]() |
fa69f38ab1 | ||
![]() |
d3b06080eb | ||
![]() |
749ab330ff | ||
![]() |
677c6555e8 | ||
![]() |
c62f6ef139 | ||
![]() |
0c34625782 | ||
![]() |
e86fa6f9fa | ||
![]() |
e2e975f357 | ||
![]() |
8eaf6d3f88 | ||
![]() |
1cb036941e | ||
![]() |
a53ff37e89 | ||
![]() |
dc3ee35c8b | ||
![]() |
e05fe94650 | ||
![]() |
b0cb0b06a2 | ||
![]() |
a9fed1c203 | ||
![]() |
6d8407676d | ||
![]() |
1157716c9c | ||
![]() |
861615b7a4 | ||
![]() |
e12d11264e | ||
![]() |
6d7b4810da | ||
![]() |
585b747612 | ||
![]() |
55887e1a89 | ||
![]() |
39a7a1298a | ||
![]() |
90059c5ae6 | ||
![]() |
ad63179484 | ||
![]() |
6eaba9d3a8 | ||
![]() |
11ce68902c | ||
![]() |
70ca059e0f |
1
Makefile
@@ -20,6 +20,7 @@
|
||||
# - nuget: restore NuGet packages
|
||||
# - 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
|
||||
#
|
||||
# - distclean: run a 'git clean -xdff'. Remove everyhing that is not in the git tree.
|
||||
# - clean: all clean_* targets below
|
||||
|
@@ -1,5 +0,0 @@
|
||||
As of December 2017, Google does not accept the use of Accessibility services for anything except helping people with disabilities. This means that Keepass2Android can no longer provide the accessibility service based AutoFill feature. Otherwise, Google would remove Keepass2Android from Play Store.
|
||||
|
||||
If you want to continue using this feature, please [install the Accessibility service based AutoFill plugin](https://github.com/PhilippC/kp2a_accservice_autofill/releases/).
|
||||
|
||||
After installation, please enable the accessibility service "KP2A AutoFillPlugin" in the Android system settings. When trying to use the plugin for the first time, KP2A will ask you if the plugin may access the Keepass database. Please accept this to use the plugin.
|
@@ -1,18 +0,0 @@
|
||||
# Installing dictionaries
|
||||
Keepass2Android will load dictionaries for your current language both from AnySoftKeyboard and from Hacker's keyboard.
|
||||
* For AnySoftKeyboard dictionaries visit: [https://play.google.com/store/search?q=anysoftkeyboard+dictionary&c=apps](https://play.google.com/store/search?q=anysoftkeyboard+dictionary&c=apps)
|
||||
* For Hacker's Keyboard dictionaries visit: [https://play.google.com/store/search?q=hacker%27s%20keyboard%20dictionary&c=apps](https://play.google.com/store/search?q=hacker%27s%20keyboard%20dictionary&c=apps)
|
||||
|
||||
# Automatic keyboard switching (requires ADB)
|
||||
Starting with Keepass2Android 1.02-pre1, you can use the [KeyboardSwap Plugin](https://play.google.com/store/apps/details?id=keepass2android.plugin.keyboardswap2) to switch to the KP2A keyboard automatically instead of bringing up the Input method selection dialog (e.g. after using the Share URL feature). To setup the plugin please follow the instructions on [the PlayStore website](https://play.google.com/store/apps/details?id=keepass2android.plugin.keyboardswap2)
|
||||
|
||||
# Deprecated: Automatic keyboard switching on rooted devices
|
||||
|
||||
In order to automatically switch to the KP2A keyboard and back, you need to
|
||||
* have a rooted device (per Android security policies)
|
||||
* have at least KP2A version 0.9.3-pre2
|
||||
* install the Secure Settings app with the "System+" module available in Secure Settings v. 1.3.4 and above. **Note:** This is no longer available for recent Android versions.
|
||||
* Go to the KP2A keyboard settings. Enable auto-switch on rooted devices.
|
||||
|
||||
If you go to a website, select "Share URL" -> Keepass2Android, the keyboard should be switched as soon as you locate the entry or when it's found automatically.
|
||||
|
@@ -1,239 +0,0 @@
|
||||
# How to build Keepass2Android
|
||||
|
||||
## Overview
|
||||
|
||||
Keepass2Android is a Mono for Android app. This means that you need Xamarin's Mono for Android to build it. However, it also uses several components written in Java, so there are also Android-Studio projects involved. To make things even worse, parts of the keyboard and kdb-library are written in native code.
|
||||
|
||||
To build KP2A from scratch, you need:
|
||||
- Xamarin's Mono for Android (also included in Visual Studio)
|
||||
- Android SDK & NDK
|
||||
|
||||
Prior to building Keepass2Android, you need to build some of its components (from command line). Then you can build the full project either through Visual Studio, or through command line.
|
||||
|
||||
By using the command line, you can build on Windows, macOS or Linux.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Common to all architectures
|
||||
- Install Android SDK & NDK (either manually with Google's [sdkmanager](https://developer.android.com/studio/command-line/sdkmanager), or through Android Studio). Visual Studio also installs a version of it, but in the end the directory must be writable and in a path without spaces (see below) so as to be able to build the components.
|
||||
- Fetch the main repository of Keepass2Android and all submodules
|
||||
- Note that VisualStudio can do this for you, otherwise run:
|
||||
- `git submodule init && git submodule update`
|
||||
|
||||
### On Windows or macOS
|
||||
- Install Visual Studio (for example 2019) with Xamarin.Android (ie. with capability to build Android apps). This should provide the needed tools like
|
||||
- Xamarin.Android
|
||||
- MSBuild
|
||||
- Java JDK
|
||||
- If you plan to build also from the command line:
|
||||
- Install the MSVC build tools of visual studio. They provide the `vcvarsall.bat` file which among other things adds MSBuild to the PATH.
|
||||
- Install [NuGet](https://www.nuget.org/downloads) to build also with "make". Alternatively, on Windows, if you use [chocolatey](https://chocolatey.org), run as administrator:
|
||||
- `choco install nuget.commandline`
|
||||
- Check that you have access to 'GNU make'.
|
||||
- On Windows, it is usually not available by default. But the Android NDK provides it. You can find it in `%ANDROID_NDK_ROOT%\prebuilt\windows-x86_64\bin\make.exe`. Alternatively, on Windows, if you use [chocolatey](https://chocolatey.org), run as administrator:
|
||||
- `choco install make`
|
||||
- On macOS, it is usually only installed if you have developer command line tools installed or if you use [homebrew](https://brew.sh) or [macports](https://www.macports.org/). As an alternative it may be available in the Android NDK at `%ANDROID_NDK_ROOT%/prebuilt/darwin-x86_64/bin/make`.
|
||||
|
||||
### On Linux
|
||||
- Install Java's JDK
|
||||
- On Debian, for example: `apt install default-jdk-headless`.
|
||||
|
||||
- Install [Mono](https://www.mono-project.com/)
|
||||
- This should provide `msbuild` & `xabuild` binary
|
||||
- On Debian, after having added the repo from above, install with `apt install -t <repo_name> mono-devel msbuild`. A value for `<repo_name>` could be `stable-buster` for example, depending on which one you chose. You could also install the `mono-complete` package if you prefer.
|
||||
|
||||
- Install Xamarin.Android
|
||||
- ~~Option 1: Use the mono-project [CI builds](https://dev.azure.com/xamarin/public/_build/latest?definitionId=48&branchName=main&stageName=Linux)~~ **NOTE:** KP2A now requires Xamarin.Android v13, which is newer than the current CI build; until a more recent CI build is available, this option is unfortunately no longer viable.
|
||||
- Option 2: [Build it from source](https://github.com/xamarin/xamarin-android/blob/master/Documentation/README.md#building-from-source)
|
||||
|
||||
- Install NuGet package of your distribution
|
||||
- On Debian/Ubuntu: `apt install nuget`
|
||||
|
||||
- Install [libzip](https://libzip.org/) for your distribution for some Xamarin.Android versions
|
||||
- This may not be relevant anymore: for example, with Xamarin.Android 11.4.99. this is not needed.
|
||||
- Some versions of Xamarin may require `libzip4`. If you are in this case:
|
||||
- On Debian/Ubuntu, install it with `apt install libzip4`.
|
||||
- Other distributions ship only `libzip5`. As a dirty workaround, it's possible to symlink `libzip.so.5` to `libzip.so.4`. Luckily, it appears to be working. For example:
|
||||
- `sudo ln -s /usr/lib/libzip.so.5 /usr/lib/libzip.so.4`
|
||||
- or `sudo ln -s /usr/lib64/libzip.so.5 /usr/lib/libzip.so.4`
|
||||
|
||||
## Building the required components:
|
||||
|
||||
This is done on the command line and requires the Android SDK & NDK and Java JDK.
|
||||
|
||||
### On Windows
|
||||
- Setup your environment:
|
||||
- Set these environment variables for Android's SDK & NDK
|
||||
- `ANDROID_HOME` (for example `set ANDROID_HOME=C:\PATH\TO\android-sdk`)
|
||||
- `ANDROID_SDK_ROOT` (for example `set ANDROID_SDK_ROOT=C:\PATH\TO\android-sdk`)
|
||||
- `ANDROID_NDK_ROOT` (for example `set ANDROID_NDK_ROOT=C:\PATH\TO\android-sdk\ndk\version`)
|
||||
|
||||
**Note:** Care must be taken when setting the above variables to **not** include a trailing backslash in the path. A trailing backslash may cause `make` to fail.
|
||||
|
||||
**Note**: If the path to the Android SDK contains spaces, you **must** do one of these:
|
||||
- either put the Android SDK into a path without spaces.
|
||||
- or create a symlink to that path which doesn't contain spaces. Attention: this requires **administrator** priveleges. For example:
|
||||
|
||||
```
|
||||
IF NOT EXIST C:\Android ( MKDIR C:\Android ) &&
|
||||
MKLINK /D C:\Android\android-sdk "C:\Program Files (x86)\Android\android-sdk"
|
||||
```
|
||||
This is because [Android NDK doesn't support being installed in a path with spaces](https://github.com/android/ndk/issues/1400).
|
||||
|
||||
**Note**: The Android SDK path will require to be writeable because during the build, some missing components might be downloaded & installed.
|
||||
|
||||
- If you have "GNU make" available on your windows system, you may build by using the Makefile. You can also find a `make` executable in `%ANDROID_NDK_ROOT%\prebuilt\windows-x86_64\bin\make.exe`. To use it, see the instructions for Linux/macOS. Basically, just run `make` or `mingw32-make` depending on which distribution of GNU make for windows you have installed.
|
||||
|
||||
- Otherwise proceed as below:
|
||||
|
||||
1. Build argon2
|
||||
|
||||
```
|
||||
cd src/java/argon2
|
||||
%ANDROID_NDK_ROOT%/ndk-build.cmd
|
||||
```
|
||||
1. Build the other java components
|
||||
|
||||
```
|
||||
cd src/build-scripts
|
||||
build-java.bat
|
||||
```
|
||||
|
||||
`build-java.bat` will call `gradlew` for several Java modules.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- For building the java parts, it is suggested to keep a short name (e.g. "c:\projects\keepass2android") for the root project directory. Otherwise the Windows path length limit might be hit when building.
|
||||
- Before building the java parts, make sure you have set the ANDROID_HOME variable or create a local.properties file inside the directories with a gradlew file. It is recommended to use the same SDK location as that of the Xamarin build.
|
||||
- On some environments, `make` can fail to properly use the detected `MSBUILD` tools. This seems to be due to long pathnames and/or spaces in pathnames. It may be required to explicitly set the `MSBUILD` path using 8.3 "short" path notation:
|
||||
- Determine the location of `MSBUILD` (e.g. `C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe`)
|
||||
- [Generate the "short" path](https://superuser.com/a/728792) of that location (e.g.: `C:\PROGRA~1\MICROS~2\2022\COMMUN~1\MSBuild\Current\Bin\MSBuild.exe`)
|
||||
- When running `make` specify the location of ``MSBUILD` explicitly (e.g.: `make MSBUILD="C:\PROGRA~1\MICROS~2\2022\COMMUN~1\MSBuild\Current\Bin\MSBuild.exe`
|
||||
|
||||
|
||||
### On Linux/macOS
|
||||
|
||||
- Setup your environment:
|
||||
- Set these environment variables for Android's SDK & NDK
|
||||
- `ANDROID_HOME` (for example `export ANDROID_HOME=/path/to/android-sdk/`)
|
||||
- `ANDROID_SDK_ROOT` (for example `export ANDROID_SDK_ROOT=/path/to/android-sdk/`)
|
||||
- `ANDROID_NDK_ROOT` (for example `export ANDROID_NDK_ROOT=/path/to/android-sdk/ndk/version`)
|
||||
|
||||
- Update your PATH environment variable so that it can access `nuget`, `msbuild` or `xabuild` (for linux):
|
||||
- On Linux:
|
||||
- add `xabuild` to your path: `export PATH=/path/to/xamarin.android-oss/bin/Release/bin/:$PATH`
|
||||
- On macOS:
|
||||
- you may similarly need to add `msbuild` & `nuget` to your PATH.
|
||||
|
||||
- Start the build:
|
||||
- This will use the Makefile at the root of the project (requires GNU make). To build everything (components & Keepass2Android APK) in a single command simply run:
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
- Otherwise, if you prefer to do step by step
|
||||
|
||||
1. Build argon2
|
||||
|
||||
```
|
||||
make native
|
||||
```
|
||||
|
||||
1. Build the other java components
|
||||
|
||||
```
|
||||
make java
|
||||
```
|
||||
|
||||
## Building Keepass2Android:
|
||||
|
||||
These are the basic steps to build Keepass2Android. You can also build Keepass2Android Offline. For this, configure the build by using the [Flavors](#Flavors).
|
||||
|
||||
### With Visual Studio
|
||||
|
||||
- On windows or on macOS open the src/KeePass.sln file with visual studio, and choose to build the project named 'keepass2android-app'
|
||||
|
||||
### Command Line
|
||||
|
||||
#### Windows, Macos & Linux
|
||||
to build the APK, simply run:
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
or to skip building the APK:
|
||||
|
||||
```
|
||||
make msbuild
|
||||
```
|
||||
|
||||
## Where is the APK ?
|
||||
The Apk can be installed on a device.
|
||||
It is located in `src/keepass2android/bin/*/*-Signed.apk`
|
||||
|
||||
If you build with Visual Studio, the APK is not produced automatically. You need to perform some extra step. See the documentation of Visual Studio on how to proceed.
|
||||
|
||||
## Flavors
|
||||
|
||||
Keepass2Android is distributed in two flavors.
|
||||
- Keepass2Android (aka `net`)
|
||||
- Keepass2Android Offline (aka `nonet`)
|
||||
|
||||
The flavor is set through a MSBuild Property named "`Flavor`". The possible values are '`Net`' and '`NoNet`'.
|
||||
|
||||
The value of the Flavor property is used in 2 projects:
|
||||
- `keepass2android-app` (in `src/keepass2android`)
|
||||
- `Kp2aBusinessLogic` (in `src/keepass2android`)
|
||||
|
||||
Its value is set inside the `*.csproj` file (XML format) of each project in the `Project`/`PropertyGroup`/`Flavor` node.
|
||||
By default its value is set to an empty string so that development is made with `AndroidManifest_debug.xml` on the '`net`' flavor.
|
||||
|
||||
This is the behaviour of the build system depending on the value of Flavor:
|
||||
| Flavor | What is built | `AndroidManifest.xml` used |
|
||||
| ----- | ----- | ----- |
|
||||
| `` (empty string): This is the default value. | Keepass2Android | `AndroidManifest_debug.xml` |
|
||||
| `Net` | Keepass2Android | `AndroidManifest_net.xml` |
|
||||
| `NoNet` | Keepass2Android Offline | `AndroidManifest_nonet.xml` |
|
||||
|
||||
### Select/Change flavor:
|
||||
|
||||
When building, by default, the flavor is not set. So the value used is the value of the Flavor property in *.csproj file. This should result on doing a build of the 'net' flavor.
|
||||
|
||||
You can force the Flavor by setting the Flavor property.
|
||||
|
||||
Proceed this way:
|
||||
|
||||
#### Command line
|
||||
|
||||
##### Windows, Macos & Linux
|
||||
|
||||
To force building 'net' with `make`, run:
|
||||
|
||||
```
|
||||
make Flavor=Net
|
||||
```
|
||||
|
||||
To build 'nonet' with `make`, run:
|
||||
|
||||
```
|
||||
make Flavor=NoNet
|
||||
```
|
||||
|
||||
##### MSBuild
|
||||
|
||||
To build with MSBuild directly on the command line, set the flavor with `-p:Flavor=value` argument. For example:
|
||||
|
||||
```
|
||||
MSBuild src/KeePass.sln ... -p:Flavor=NoNet
|
||||
```
|
||||
|
||||
#### Visual Studio
|
||||
When building with Visual Studio, edit the `*.csproj` file (XML format) and set the value in the `Project`/`PropertyGroup`/`Flavor` node. This is needed only for the projects that use the flavors.
|
||||
|
||||
**Note:** When switching between flavors, be sure to clean the previous build before.
|
||||
|
||||
## Makefile
|
||||
|
||||
It is possible to override the project's default 'Flavor' (Net, NoNet) and 'Configuration' (Release, Debug) by passing it as argument to `make`. See the header of the Makefile to see what can be done.
|
@@ -1,118 +0,0 @@
|
||||
<div class="wikidoc">
|
||||
<h1>Comparison of Keepass apps for Android</h1>
|
||||
<p>This page was created to give you a short overview of the features of Keepass2Android vs. Keepass2Android Offline. As Keepass2Android is based on Keepassdroid (by Brian Pellin), there are quite a few similarities here, as well, so we added Keepass2Android
|
||||
vs. Keepassdroid comparison as well. "Better" properties are highlighted in bold. The page was created in 12/2013. If any information is out of date, please leave a note in the comments section.</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>
|
||||
<h3>Keepass2Android</h3>
|
||||
</th>
|
||||
<th>
|
||||
<h3>Keepass2Android Offline</h3>
|
||||
</th>
|
||||
<th>
|
||||
<h3>Keepassdroid</h3>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Play Store link</strong></td>
|
||||
<td><a href="https://play.google.com/store/apps/details?id=keepass2android.keepass2android">https://play.google.com/store/apps/details?id=keepass2android.keepass2android</a></td>
|
||||
<td><a href="https://play.google.com/store/apps/details?id=keepass2android.keepass2android_nonet">https://play.google.com/store/apps/details?id=keepass2android.keepass2android_nonet</a></td>
|
||||
<td><a href="https://play.google.com/store/apps/details?id=com.android.keepass">https://play.google.com/store/apps/details?id=com.android.keepass</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Technology</strong></td>
|
||||
<td>Mono for Android, Java</td>
|
||||
<td>Mono for Android, Java</td>
|
||||
<td>Java</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Keepass 2.x (kdbx) support</strong></td>
|
||||
<td><strong>write and read</strong></td>
|
||||
<td><strong>write and read</strong></td>
|
||||
<td>read (write in beta)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Keepass 2.x feature support</strong></td>
|
||||
<td><strong>Viewing and editing of tags, attachments, additional fields, TAN support</strong></td>
|
||||
<td><strong>Viewing and editing of tags, attachments, additional fields, TAN support</strong></td>
|
||||
<td>Edit standard fields only, no display/edit of attachments</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Keepass 1.x (kdb) support</strong></td>
|
||||
<td>currently read-only</td>
|
||||
<td>currently read-only</td>
|
||||
<td><strong>yes</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Storage locations</strong></td>
|
||||
<td>SD Card,<strong> Cloud (Dropbox, Google Drive, OneDrive), Web (FTP/HTTP/HTTPS/WebDav), SFTP</strong></td>
|
||||
<td>SD Card</td>
|
||||
<td>SD Card</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>File choosers</strong></td>
|
||||
<td><strong>Internal or third party</strong></td>
|
||||
<td><strong>Internal or third party</strong></td>
|
||||
<td>Third party</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Required permissions</strong></td>
|
||||
<td>Internet, SD card, Manage accounts</td>
|
||||
<td><strong>SD card</strong></td>
|
||||
<td><strong>SD card</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Android 4.x style</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Database locking</strong></td>
|
||||
<td><strong>QuickUnlock</strong> or full lock</td>
|
||||
<td><strong>QuickUnlock</strong> or full lock</td>
|
||||
<td>Full lock only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Browser integration</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Secure keyboard</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Database unlocking</strong></td>
|
||||
<td><strong>password, keyfile, One-Time-Passwords (supports Yubikey NEO with NFC), KeeChallenge</strong></td>
|
||||
<td><strong>password, keyfile, One-Time-Passwords (supports Yubikey NEO with NFC), KeeChallenge</strong></td>
|
||||
<td>password, keyfile</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Keepass plugin compatibility</strong></td>
|
||||
<td><strong>Twofish Cipher, KeeChallenge, TrayTOTP, KeeOTP</strong></td>
|
||||
<td><strong>Twofish Cipher, KeeChallenge, TrayTOTP, KeeOTP</strong></td>
|
||||
<td>Twofish Cipher</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Extensible with plugins</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Database export</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p> </p>
|
||||
</div><div class="ClearBoth"></div>
|
@@ -1,125 +1 @@
|
||||
**Note:** This is an incomplete and preliminary documentation. More documentation will be added as requests come in or when the app is more feature stable.
|
||||
If you want, I'd be happy if you contribute texts for this place!
|
||||
|
||||
If you think something is missing in the documentation, please create an issue at https://github.com/PhilippC/keepass2android/issues
|
||||
|
||||
# What you should know and think about
|
||||
If you store important information using Keepass2Android, you should know a little bit about what's going on:
|
||||
* Keepass2Android stores your password in an encrypted file. It is *your responsibility* to backup this file regularly and safely.
|
||||
* There is no way for anyone, including the app's author, to access the information stored in your password database without
|
||||
* having the database file
|
||||
* knowing the master password (and additional second factor if you chose one)
|
||||
This means that **if you forget the master password, your database is lost**! So make sure you remember the password and retain any second factor method (if one is used).
|
||||
* You might also want to think about:
|
||||
* What happens if I have an accident? Should any trusted person be able to access my database?
|
||||
* What happens if my phone gets lost or stolen? Do I know how to recover my database from a backup or the cloud?
|
||||
|
||||
|
||||
# Getting started
|
||||
|
||||
## Opening an existing database
|
||||
Many users are already using Keepass 2 on Windows and thus have their passwords stored in a Keepass database, typically a file with ending .kdbx. For opening such an existing database, there are two main options:
|
||||
* You can open the file directly if it is located on a webserver or in the cloud. Use "Open Database" on the startscreen. By default, files from the cloud or servers are cached in the application's cache directory after loading them once. This allows to access your files even when you're offline.
|
||||
* If you don't have your database stored on a webserver or in the cloud (or if you're using KP2A Offline) you need to copy your kdbx-Database to your phone. I suggest to use a sync tool like FolderSync. Such a tool copies your database to your local storage, so you always have it accessible. FolderSync can access your database if you have it on a network share or use any other common storage.
|
||||
|
||||
## Creating a new database
|
||||
Select "Create new database" from the start screen. Tap the integrated help icons for more information. Note that by default, the database is created as a local file. Please consider making backups regularly or select a location in the cloud.
|
||||
|
||||
## Getting passwords into password fields
|
||||
There are many ways how to enter the passwords from your database in the corresponding fields. By default, the clipboard as well as the KP2A keyboard are activated in the settings:
|
||||
* The KP2A keyboard is the recommended way because it's safe against clipboard loggers: Whenever you select an entry, the KP2A keyboard notification will appear in the notification bar. Click it to activate the keyboard. (The first time you do this, you are required to enable the keyboard in the system settings. This must be done by the user for Android security reasons.) As soon as it's activated, you can tap a field where you want to enter data from the selected entry. The KP2A keyboard will come up. Click the KP2A key (on the bottom left) to select whether you want to enter Username/password etc. When you're done, click the Keyboard key (next to the KP2A key) to switch back to your favorite keyboard.
|
||||
* You can enable the Keepass2Android Autofill service in the system's Autofill settings (Android 8+) which allows to fill data using Android's accessibility system. This works with many apps including Firefox browser but is not supported for Chrome (when writing this).
|
||||
* The clipboard based approach can be used as well: Pull the notification bar down and select "Copy username/password to clipboard". Then long-tap the field where you want to paste the data. A small "paste" button should come up. Note, however, that information in the clipboard can be monitored by all apps on your device and clearing the clipboard is not always possible.
|
||||
|
||||
These options can be used in different workflows:
|
||||
### Browser-based workflow
|
||||
If you are browsing the web and need to enter crendentials for a webpage, a simple and powerful workflow is to use the "Share URL" option from the browser's menu. Then select Keepass2Android (or KP2A Offline). Open your database (if it's not already opened) and select the entry you want to enter (if KP2A did not already select the appropriate entry). Use the built-in keyboard or the clipboard to enter the password.
|
||||
### Autofill service based workflow
|
||||
If you have enabled the autofill service and open a (supported) app with a password field, a dropdown appears. Select "Fill with Keepass2Android" to select the appropriate entry. When you return to the app, the password and user field should be filled already.
|
||||
### KP2A based workflow for websites
|
||||
Open KP2A, open your database, select your entry (in this step, the notification bar items should show up already). Now click the URL link of the entry to open a browser window with the website. Use one of the methods described above to enter the credentials.
|
||||
### KP2A Keyboard based workflow
|
||||
When you are in a text field, you can use the Android icon in the notification bar to switch to the KP2A keyboard. Hit the KP2A key to select an icon. After it's selected, hit the KP2A key again to enter the desired field.
|
||||
|
||||
## Creating a new account
|
||||
Assume you want to create an account on a website. If you do not have a database yet, see above. As soon as you have a database, you may proceed as follows:
|
||||
* Go to the website you want to create the account for
|
||||
* Select Share/Share URL from the browser's menu and tap "Keepass2Android"
|
||||
* Log in to your database (if it's not already unlocked)
|
||||
* You will see the search result screen with "No search results"
|
||||
* Tap "Create entry for URL"
|
||||
* Choose the desired group, then tap the "+"-button to add an entry.
|
||||
* Tap the "..." button next to the password field to launch the password generator, create your password and then select "Accept"
|
||||
* Enter a name for the entry
|
||||
* Enter the username you want to use for the entry
|
||||
* Tap "Save" on the top
|
||||
* You should see notifications like "Entry is available through KP2A keyboard" and/or "Copy username/password to clipboard". If not, view the new entry by clicking it.
|
||||
* Return back to the browser.
|
||||
* Use the notifications to enter your new credentials. See "Getting passwords into the password fields" for more details.
|
||||
* If the user name you entered is not available or valid, choose a different one but copy it to clipboard. After creating the account, don't forget to update the new entry.
|
||||
|
||||
# Keepass2Android vs Keepass2Android Offline vs Keepassdroid
|
||||
What's the difference between these apps? There is a short comparison on [Comparison of Keepass apps for Android](Comparison-of-Keepass-apps-for-Android.md) to help you pick the best for you!
|
||||
|
||||
# Advanced topics
|
||||
## YubiKey NEO support for One-Time-Passwords
|
||||
Please see the [How to use Keepass2Android with YubiKey NEO](How-to-use-Keepass2Android-with-YubiKey-NEO.md) page.
|
||||
|
||||
## Advanced usage of the Keepass2Android keyboard
|
||||
Please see the [Advanced usage of the Keepass2Android keyboard](Advanced-usage-of-the-Keepass2Android-keyboard.md) page.
|
||||
|
||||
## Using Keepass2Android like an authenticator app to generate Time-based One-Time-Passwords (TOTPs)
|
||||
Please see [Generating TOTPs with Keepass2Android](Generating-TOTPs.md)
|
||||
|
||||
# FAQ
|
||||
|
||||
## Should I use the KP2A keyboard for entering passwords?
|
||||
The KP2A keyboard is meant to quickly "paste" or "type" values from your database to any text fields by using the KP2A icon. The QUERTY keyboard is just for convenience (if you just have the KP2A keyboard activated and need to enter a few letters). However, every other (trustworthy) keyboard is ok as well to enter sensitive information: Keyboard's aren't unsafe in Android. Only the clipboard is. Thus, the KP2A keyboard allows to get information out of the database without using the clipboard.
|
||||
**You can use any keyboard when you enter the main database password**
|
||||
|
||||
## Is it safe to store my kdbx file in the cloud?
|
||||
While it may happen that someone gets access to your kdbx file in the cloud, there is still no need to worry: the purpose of encryption is to protect the data even in case someone gets the kdbx file! As long as you are using a safe master key, you're safe! [Key files](https://keepass.info/help/base/keys.html#keyfiles) can help with securing the database even more.
|
||||
|
||||
## Doesn't Keepass2Android create automatic backups?
|
||||
Yes and no. Yes: Keepass2Android stores the last successfully opened file as a read-only backup locally on the phone (unless you disable this is in the settings). This should make sure that even if the file gets destroyed during a save operation or gets deleted by accident, you should always have a version that can be opened. (Don't mix this up with the internal file cache which is not meant as a backup and can easily be overwritten even with a corrupt file. This internal file cache is meant for providing writable access even when the original file is not reachable, e.g. when you're offline.)
|
||||
No: The local backup has two shortcomings: It is only one backup and does not allow to revert to older versions. So if you deleted an entry from the database, it might be deleted in the local backup soon as well. The even more important shortcoming is that it is just a local backup. It won't help when your phone gets lost or broken. Please create additional backups on seperate storage!
|
||||
|
||||
## How do I backup the database?
|
||||
If you have stored your database on the cloud, you might rely on your cloud storage providers backups. Make sure they allow you to revert to older revisions in case the file gets corrupted for some reason.
|
||||
If you are working with a local database file, make sure you create regular backups. I suggest you have an aumotated mechanism, e.g. with FolderSync (Lite) which can copy local files from your device to other locations, e.g. your PC in a local network. You can also use USB or tools like MyPhoneExploror to transfer data to your PC. Or, you use a removable storage like an SD card which you keep in a safe place after making the backup.
|
||||
In all cases, you need to verify that your backup is readable! It's even best to test this on another device (e.g. a PC), so you simulate the case that you may lose your phone.
|
||||
|
||||
## I can open my database with fingerprint, but don't remember my master password!
|
||||
It's time for action! As soon as possible, select Settings - Database - Export and choose unencrypted XML (don't put this on the cloud but on a local file). Transfer this file to a PC and import it to a new kdbx file, e.g. with Keepass2. Choose a new master password and make sure you don't forget this password!
|
||||
|
||||
## How can I transfer data from one device to another?
|
||||
* If you are about to get a new Android device, you should make sure you're not losing your passwords in the transition! The first thing you need to make sure is that you can access your .kdbx file (which stores the passwords) on the new device. If it is already stored in the cloud, you only need to make sure you know how to setup the cloud storage on the new device (it might require a password, so make sure you have access to that!).
|
||||
* If the .kdbx-file is stored locally on the old device, make sure you have an up-to-date backup (see above). You can then transfer that backup copy to the new device. (Note: transferring via USB causes data corruption in some cases, use MyPhoneExplorer or similar tools to be sure this does not happen.)
|
||||
* If you are securing your password database with a keyfile, also transfer this key file to the new device.
|
||||
* If you are opening your database with a fingerprint, make sure you also know the master password because fingerprint will not be available immediately on the new device.
|
||||
|
||||
## Why is Keepass2Android's apk so big?
|
||||
Please see [Keepass2Android Apk](Keepass2Android-Apk.md) for more information.
|
||||
|
||||
## I get a message "File is trashed" when reading or writing a file on Google Drive
|
||||
This happens because ocaml-fuse (I guess you are on Linux and use that) moves files to trash and then creates a new one instead of correctly updating the file on Google Drive (each file has a unique ID which Keepass2Android uses). Fortunately, this was fixed: https://github.com/astrada/google-drive-ocamlfuse/issues/494. After activating this option, please select "Change database" in KP2A, tap ,"Open file" and browse to the file on Google Drive again. After that, the message should no longer pop up.
|
||||
|
||||
## I get a message "The name must not be empty: null" when opening from Google Drive
|
||||
Please follow these steps:
|
||||
|
||||
* select "Change database" on the password screen, then "Open database" and browse to your file again
|
||||
* go to Android app settings and disable all permissions for the KP2A app. Then try again to open the database file.
|
||||
* reboot the device
|
||||
|
||||
(Before running the following steps, make sure you don't have local changes in your database which have not been synchronized with Google Drive (this can happen if you worked offline). If you have, please open the database from the local cache and go to settings - database settings - export database and make a backup copy of the data.)
|
||||
|
||||
* clear KP2A's app cache in the Android settings
|
||||
* uninstall & reinstall
|
||||
|
||||
One of these has helped all users so far, but unfortunately it's not totally clear to me why different steps are required (or nothing for most users).
|
||||
|
||||
# For developers
|
||||
If you are interested in adding new features, you have two options:
|
||||
Either your features can be implemented as a plug-in. Please see [How to create a plug-in?](How-to-create-a-plug-in_.md) for more information. Or you add the features directly in the source code of the projects and create a pull request.
|
||||
|
||||
If you want to build Keepass2Android, check the [build guide](Build.readme.md).
|
||||
This page has been moved to the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation)
|
@@ -1,53 +1 @@
|
||||
|
||||
## TOTP in brief
|
||||
TOTP stands for [Time-based One-Time Password algorithm](https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm) which is one of the most common way proposed by websites to do a [two-factor authentication (2FA)](https://en.wikipedia.org/wiki/Multi-factor_authentication).
|
||||
|
||||
On these websites, this option will often be mentioned in the 2FA configuration menu as things like "_use code generated by an application_", "_use [Google] Authenticator app_".
|
||||
|
||||
You're prompted to scan a QR code with the app, which essentially contains a code called "_seed_", usually with a form like "_AZER TYUI OPQS DFGH JKLM_", used to generate TOTPs. The seed can be also directly copied if there is no scanning option on the app.
|
||||
|
||||
Most common apps:
|
||||
|
||||
- Google Authenticator
|
||||
- Authy
|
||||
- Microsoft Authenticator
|
||||
- FreeOTP
|
||||
- LastPass Authenticator
|
||||
|
||||
## TOTP in KeePass and benefits
|
||||
In KeePass (by Dominik Reichl) there is are several ways to enable this Authenticator app ability:
|
||||
|
||||
- built-in TOTP support: https://keepass.info/help/base/placeholders.html#otp
|
||||
- [KeePassOTP plugin](https://keepass.info/plugins.html#kpotp)
|
||||
- [KeeOtp plugin](https://keepass.info/plugins.html#keeotp)
|
||||
- [KeeTrayTOTP plugin](https://keepass.info/plugins.html#keetraytotp) (note the name "_TrayTOTP_" on this one for later)
|
||||
|
||||
KeePassXC also supports TOTP: https://keepassxc.org/docs/KeePassXC_UserGuide#_adding_totp_to_an_entry
|
||||
|
||||
The greatest benefits are:
|
||||
|
||||
- the seed stays available contrary to the above apps (for which it's more or less hard to backup/restore/switch with another app)
|
||||
- TOTPs are available wherever the KeePass database is available. But conceptually it's not really 2FA anymore (all things are stored in the same place).
|
||||
|
||||
The different implementations use different ways of storing the TOTP seed (or secret, or key) and optional settings (e.g. the length of the TOTP to generate) within an entry inside the kdbx database. Keepass2Android attempts to be able to read the different formats, but can only write one:
|
||||
|
||||
## TOTP in Keepass2Android
|
||||
|
||||
If you use any of the tools mentioned above, you can set up TOTP entries with them. Keepass2Android can read those entries and generate TOTPs if any of the following styles are used:
|
||||
|
||||
* Keepass2 style: used when there are TimeOtp-Secret(-XXX) fields in the entry
|
||||
* KeeOtpPlugin style: used when there is an otp field containing a query string in the form of key=abc&step=X&size=Y (step and size are optional)
|
||||
* KeeWebOtp/Key Uri Format style: used when entry contains a URL starting with otpauth://totp/, e.g. otpauth://totp/?secret=abc (https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
|
||||
* KeeTrayTotp style:
|
||||
* requires a non-empty seed field (default key is "TOTP seed", can be changed in KP2A settings), value is base32 encoded data
|
||||
* requires a non-empty settings field (default key is "TOTP Settings", can be changed as well), value is expected to be a csv-separated array with [Duration];Length(;TimeCorrectionURL). Length is either an integer value or "S" to indicate Steam encoding
|
||||
|
||||
In order to view the generated TOTP code in KP2A, open the corresponding entry. You can then
|
||||
* use a dynamically generated field called "_TOTP_" containing the TOTP or
|
||||
* use the "Copy TOTP" button on the system notification for the selected entry or
|
||||
* switch to the KP2A keyboard and use the TOTP button to insert the TOTP value into the target app or browser
|
||||
|
||||
If you want to configure an entry to contain the TOTP fields, it is suggested to enter edit mode for the entry. Then click the "Configure TOTP" button. You can either enter the data manually or scan a QR code with the information.
|
||||
|
||||
### Spaces in otp field
|
||||
Make sure that the URI doesn't contain spaces, otherwise KeePass2Android will fail to generate TOTPs as a space is an invalid character. If your URIs have spaces, check [this comment](https://github.com/PhilippC/keepass2android/issues/1248#issuecomment-628035961)._
|
||||
This page has been moved to the [wiki](https://github.com/PhilippC/keepass2android/wiki/Generating-TOTPs)
|
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 87 KiB |
@@ -1,157 +0,0 @@
|
||||
# How to create a plug-in or connect from your app
|
||||
|
||||
Creating a plug-in for Keepass2Android or enabling your app to query credentials from Keepass2Android is pretty simple. Please follow the steps below to get started. In case you have any questions, please contact me.
|
||||
|
||||
## Preparations
|
||||
First check out the source code and import the Keepass2AndroidPluginSDK from [https://github.com/PhilippC/keepass2android/tree/master/src/java/Keepass2AndroidPluginSDK2](https://github.com/PhilippC/keepass2android/tree/master/src/java/Keepass2AndroidPluginSDK2/) into your workspace. You should be able to build this library project.
|
||||
|
||||
Now add a reference to the PluginSDK library from your existing app or add a new plug-in app and then add the reference.
|
||||
|
||||
## Authorization
|
||||
|
||||
Keepass2Android stores very sensitive user data and therefore implements a plug-in authorization scheme based on broadcasts sent between the plug-in and the host app (=Keepass2Android or Keepass2Android Offline). Before your app/plug-in gets any information from KP2A, the user will have to grant your app/plug-in access to KP2A. As not every app/plug-in requires access to all information, you must specify which scopes are required by your app. The implemented scopes can be found in [https://github.com/PhilippC/keepass2android/tree/master/src/java/Keepass2AndroidPluginSDK2/src/keepass2android/pluginsdk/Strings.java](https://github.com/PhilippC/keepass2android/tree/master/src/java/Keepass2AndroidPluginSDK2/src/keepass2android/pluginsdk/Strings.java).
|
||||
|
||||
To tell Kp2a that you're a plug-in, you need to add a simple BroadcastReceiver like this:
|
||||
|
||||
```java
|
||||
|
||||
public class PluginAAccessReceiver extends keepass2android.pluginsdk.PluginAccessBroadcastReceiver
|
||||
{
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getScopes() {
|
||||
ArrayList<String> scopes = new ArrayList<String>();
|
||||
scopes.add(Strings.SCOPE_DATABASE_ACTIONS);
|
||||
scopes.add(Strings.SCOPE_CURRENT_ENTRY);
|
||||
return scopes;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Here, you define the method getScopes where the list of scopes is created which must be granted by the user. The actual logic of the authorization process is implemented by the base class in the sdk.
|
||||
|
||||
In order to make this broadcast receiver visible to KP2A, add the following lines (probably with the name adapted to your class name) in the AndroidManifest.xml:
|
||||
|
||||
```xml
|
||||
<receiver android:name="PluginAAccessReceiver" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
|
||||
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
|
||||
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
```
|
||||
|
||||
Please also add a few strings in your resource files (e.g. strings.xml) with the following keys:
|
||||
|
||||
```xml
|
||||
<string name="kp2aplugin_title">The Great PluginA</string>
|
||||
<string name="kp2aplugin_shortdesc">Test plugin to demonstrate how plugins work</string>
|
||||
<string name="kp2aplugin_author">[your name here](your-name-here)</string>
|
||||
```
|
||||
These strings will be displayed to the user when KP2A asks if access should be granted.
|
||||
|
||||
## Modifying the entry view
|
||||
You can add menu options for the full entry or for individual fields of the entry when displayed to the user. This is done, for example, by the QR plugin ([https://play.google.com/store/apps/details?id=keepass2android.plugin.qr](https://play.google.com/store/apps/details?id=keepass2android.plugin.qr)).
|
||||
In addition, it is even possible to add new fields or modify existing fields. Please see the sample plugin "PluginA" for a simple example on how to do this:
|
||||
[https://github.com/PhilippC/keepass2android-sampleplugin/blob/main/src/keepass2android/plugina/PluginAAccessReceiver.java](https://github.com/PhilippC/keepass2android-sampleplugin/blob/main/src/keepass2android/plugina/PluginAAccessReceiver.java)
|
||||
|
||||
## Querying credentials
|
||||
KP2A 0.9.4 adds a great opportunity for third party apps: Instead of prompting the user to enter credentials or a passphrase, the app should try to get the data from KP2A if it is installed: If the user grants (or previously granted) access for the app, KP2A will automatically retrieve the matching entry. User action is only required if the KP2A database is locked (user will usually unlock it with the short QuickUnlock code) or if no matching entry is found (user can then create a new entry or select an existing one. in the latter case KP2A will offer to add entry information so that the entry will be found automatically next time).
|
||||
|
||||
To implement this, simply follow the steps descrIbed above in the sections Preparation and Authorization. Then, wherever appropriate in your app, do something like this:
|
||||
|
||||
```java
|
||||
try
|
||||
{
|
||||
PlaceholderFragment.this.startActivityForResult(
|
||||
Kp2aControl.getQueryEntryIntentForOwnPackage(),
|
||||
1);
|
||||
}
|
||||
catch (ActivityNotFoundException e)
|
||||
{
|
||||
Toast.makeText(
|
||||
PlaceholderFragment.this.getActivity(),
|
||||
"no KP2A host app found",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
(of course you can use `PacketManager` to check if the intent can be started instead of catching the `Exception`).
|
||||
|
||||
Instead of querying credentials associated with your own app, you might want to query other credentials as well. instead of `KpControl.getQueryEntryIntentForOwnPackage()` use
|
||||
`Kp2aControl.getQueryEntryIntent("google.com")`
|
||||
This requires \{"SCOPE_QUERY_CREDENTIALS (whereas getQueryEntryIntentForOwnPackage() requires SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)"\}.
|
||||
|
||||
The credential data can be retrieved in onActivityResult():
|
||||
|
||||
```java
|
||||
if ((requestCode == 1) //queryEntry for own package
|
||||
&& (resultCode == RESULT_OK)) // ensure user granted access and selected something
|
||||
{
|
||||
HashMap<String, String> credentials = Kp2aControl.getEntryFieldsFromIntent(data);
|
||||
if (!credentials.isEmpty())
|
||||
{
|
||||
//here we go!
|
||||
Toast.makeText(
|
||||
getActivity(),
|
||||
"retrieved credenitals! Username="+credentials.get(KeepassDefs.UserNameField),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that you get access to all strings (Title, Username, Password, URL, Notes + any user defined strings) in the entry. This may be in intersting in combination with the following section:
|
||||
|
||||
## Storing data in KP2A
|
||||
If you allow the user to set up an account in your app or create a password, e.g. for encryption, please add an option to store this data in the Keepass2Android database, as this will lead to great workflows for the user. It's as simple as
|
||||
|
||||
```java
|
||||
try {
|
||||
HashMap<String, String> fields = new HashMap<String, String>();
|
||||
//standard fields
|
||||
fields.put(KeepassDefs.TitleField, "plugin A");
|
||||
fields.put(KeepassDefs.UserNameField, "John Doe");
|
||||
fields.put(KeepassDefs.PasswordField, "top secret");
|
||||
//associate entry with our app. If we would require the URL field for a web URL,
|
||||
//this string could be added in any other (e.g. a custom) field
|
||||
fields.put(KeepassDefs.UrlField, "androidapp://"+getActivity().getPackageName());
|
||||
//custom field:
|
||||
fields.put(PLUGIN_A_PASSPHRASE, "some long text");
|
||||
//mark custom field as protected (i.e. display masked, enable memory protection in Keepass2)
|
||||
ArrayList<String> protectedFields = new ArrayList<String>();
|
||||
protectedFields.add(PLUGIN_A_PASSPHRASE);
|
||||
|
||||
//add to KP2A
|
||||
PlaceholderFragment.this.startActivityForResult(
|
||||
Kp2aControl.getAddEntryIntent(fields, protectedFields),
|
||||
2);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(
|
||||
PlaceholderFragment.this.getActivity(),
|
||||
"no KP2A host app found",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
```
|
||||
|
||||
Note that this does not even require access authorization because the user will actively save the entry anyways (after selecting the group where to create it.)
|
||||
|
||||
## Get information about database actions
|
||||
With {"SCOPE_DATABASE_ACTIONS"}, you will be informed when the user opens, closes, locks or unlocks the database including the file name information.
|
||||
|
||||
PluginA uses this to simply display a toast message in its ActionReceiver:
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected void dbAction(DatabaseAction db) {
|
||||
|
||||
Log.d("PluginA", db.getAction() + " in file " + db.getFileDisplayName() + " ("+db.getFilePath()+")");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Sample plugin
|
||||
Most example code from above is taken from the simple sample plugin "PluginA" as can be found on [https://keepass2android.codeplex.com/SourceControl/latest#src/java/PluginA/](https://keepass2android.codeplex.com/SourceControl/latest#src/java/PluginA/)
|
@@ -1,31 +0,0 @@
|
||||
<div class="wikidoc">
|
||||
<h1>How to use Keepass2Android with YubiKey NEO</h1>
|
||||
<p>Please refer to the documentation on the Keepass website (<a href="http://keepass.info/help/kb/yubikey.html">http://keepass.info/help/kb/yubikey.html</a>) or the Yubico website (<a href="http://www.yubico.com/applications/password-management/consumer/keepass/">http://www.yubico.com/applications/password-management/consumer/keepass/</a>)
|
||||
on how to set up a Keepass 2 database with Yubikey/OTP protection.<br>
|
||||
<br>
|
||||
After successful setup you should have the database file, e.g. yubi.kdbx, and the OTP auxiliary file, e.g. yubi.otp.xml, both in the same folder.<br>
|
||||
<a href="How to use Keepass2Android with YubiKey NEO_OTPAuxFile_2.png"><img title="OTPAuxFile" src="How to use Keepass2Android with YubiKey NEO_OTPAuxFile_thumb.png" alt="OTPAuxFile" width="513" height="40" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a></p>
|
||||
<p>Make sure you make <strong>both files</strong> available to Keepass2Android, e.g. by placing them both in your Dropbox.</p>
|
||||
<p>Now you should check your NDEF setup of the Yubikey NEO. Therefore, go to the Tools menu in the Yubico Personalization Utility. Select the same slot as used for OTPs with Keepass 2. The default setting for NDEF type and payload should work. If you experience
|
||||
problems, you may use the configuration as shown in this screenshot or simply press the “Reset” button:</p>
|
||||
<p><a href="How to use Keepass2Android with YubiKey NEO_image_2.png"><img title="image" src="How to use Keepass2Android with YubiKey NEO_image_thumb.png" alt="image" width="760" height="622" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a></p>
|
||||
<p><br>
|
||||
<br>
|
||||
In Keepass2Android, select "Open file" and locate your database file, e.g. yubi.kdbx.<br>
|
||||
<br>
|
||||
In the password screen under "Select master key type" select "Password + OTP".</p>
|
||||
<p><a href="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-50_2.png"><img title="Screenshot_2013-12-13-06-38-50" src="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-50_thumb.png" alt="Screenshot_2013-12-13-06-38-50" width="204" height="360" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a></p>
|
||||
<p>Click "Load auxiliary OTP file". This is required to load the information how many OTPs must be entered. As loading the file might require user action in some cases, this is not performed automatically.<br>
|
||||
<a href="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-12_2.png"><img title="Screenshot_2013-12-13-06-38-12" src="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-12_thumb.png" alt="Screenshot_2013-12-13-06-38-12" width="204" height="360" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a><br>
|
||||
After loading the OTP auxiliary file, you should see a few text fields for entering the OTPs. Now swipe your YubiKey NEO at the back of your Android device. If you have multiple apps which can handle NFC actions, you might be prompted to select which app to
|
||||
use. Select Keepass2Android in this case. Swipe your YubiKey again until all OTP fields are filled. Note: You don't need to select the next text field, this is done automatically!<br>
|
||||
<a href="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-36_2.png"><img title="Screenshot_2013-12-13-06-38-36" src="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-36_thumb.png" alt="Screenshot_2013-12-13-06-38-36" width="204" height="360" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a><br>
|
||||
Don't forget to also enter your password and click OK. You will see the “Saving auxiliary OTP file…” dialog. Note that there is some encryption envolved which is probably fast on your PC but might take some time on your mobile device. You
|
||||
can reduce the look-ahead window length to speed this up.<br>
|
||||
<a href="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-39-47_2.png"><img title="Screenshot_2013-12-13-06-39-47" src="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-39-47_thumb.png" alt="Screenshot_2013-12-13-06-39-47" width="204" height="360" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a></p>
|
||||
<h2> </h2>
|
||||
<h2>A note about offline access</h2>
|
||||
<p>If your database is stored in the cloud or on the web, you can still access it if you have enabled file caching (which is on by default). With OTPs, this becomes a little bit more complicated: If you repeatedly open your datbase while being offline, the
|
||||
OTP counter stored on the Yubikey will be increased. Don’t forget to synchronize the database (which will also synchronize the OTP auxiliary file) as soon as possible to avoid problems with accessing your database on other devices! If you often need
|
||||
to open the database while you’re offline, consider increasing the look-ahead window length!</p>
|
||||
</div><div class="ClearBoth"></div>
|
@@ -1,25 +0,0 @@
|
||||
Keepass2Android's apk is pretty big, e.g. when comparing to Keepassdroid. The main difference is that Keepass2Android is built on Mono for Android. Mono is an open-source implementation of the Microsoft .Net Framework (installed on pretty much every Windows PC). On Windows, the .net framework requires several hundred MB (but only once, not for every application). On Android devices, Mono is not installed globally. Instead, it is packaged into every app. The more features from Mono are required, the bigger the package becomes.
|
||||
|
||||
Here's a list of what is contained in the Keepass2Android 0.9.1 application package:
|
||||
|
||||
```
|
||||
Mono for Android
|
||||
.net dlls 5.0 MB
|
||||
Runtime 2.5 MB
|
||||
Google libraries 0.8 MB
|
||||
(for Drive support)
|
||||
|
||||
Resources Strings, Icons.. 2.1 MB
|
||||
Password Font 0.2 MB
|
||||
Java Code including Dropbox 1.1 MB
|
||||
GDrive, SkyDrive
|
||||
libraries
|
||||
|
||||
Keepass library 0.2 MB
|
||||
Keepass2Android Code 0.3 MB
|
||||
Java/Mono bindings 0.5 MB
|
||||
|
||||
rest 0.3 MB
|
||||
|
||||
TOTAL 13 MB
|
||||
```
|
@@ -1,20 +0,0 @@
|
||||
Google has introduced the Android Autofill interface in Android 8. Keepass2Android supports this interface. In most Android apps and all Autofill-enabled browsers, this is the most convenient way of entering passwords. As soon as you focus a field, you will see a popup "Fill with Keepass2Android".
|
||||
|
||||
<img src="autofill-facebook.png" />
|
||||
|
||||
After clicking this popup, you can unlock your KP2A database. If automatic look up succeeds, KP2A will close automatically, if not you are prompted to select the entry you want to auto-fill. When returning to the target app, the fields should be filled automatically already.
|
||||
|
||||
As of January 2018, the following browsers are known to have Android Autofill support:
|
||||
|
||||
* Firefox Focus / Firefox Klar
|
||||
* Opera Mini
|
||||
|
||||
These browsers do not (yet) have autofill support:
|
||||
|
||||
* Google Chrome
|
||||
* Firefox for Android ([bugzilla entry](https://bugzilla.mozilla.org/show_bug.cgi?id=1352011))
|
||||
* Brave-Browser
|
||||
* Opera
|
||||
|
||||
Please use the Share-URL-feature and the built-in KP2A keyboard for these browsers.
|
||||
|
@@ -1,25 +1 @@
|
||||
# Who we are
|
||||
|
||||
Philipp Crocoll
|
||||
Wallonenstr. 4
|
||||
76297 Stutensee
|
||||
Germany
|
||||
|
||||
is the author of Keepass2Android and Keepass2Android Offline.
|
||||
|
||||
# What data is collected?
|
||||
|
||||
The contents of your password database is yours and is never collected by us. Keepass2Android stores this data on a location chosen by the user and encrypted in the Keepass database format. The app author does not have any access, neither to the files nor the contents. Depending on the user's choice of the storage location, the files may be stored on third-party servers like Dropbox or Google Drive.
|
||||
|
||||
Keepass2Android does not collect personal identifiable information. For debugging purposes, the user may activate creating a debug log. This collects data inside the app and is not accessible to any other app nor the author of the app, unless the user explicitly sends the debug log to the author. Debug logs usually do not contain personal identifiable information, except if such information is part of file or folder names. Debug logs will not be shared with third parties unless explicitly authorized by the sender.
|
||||
|
||||
# What Android permissions are required?
|
||||
|
||||
* **Internet** (Keepass2Android regular only): Required to allow the user to read/store password databases or key files on remote locations, e.g. Dropbox or via WebDav.
|
||||
* **Contacts/Accounts** (Keepass2Android regular only): Required by the Google Drive SDK. If you want to access files on Google Drive, you are prompted to select one of the Google Accounts on your phone to use. The permission is required to query the list of Google accounts on the device. Keepass2Android does not access your personal contacts.
|
||||
* **Storage**: Required to allow the user to read/store password databases or key files on the device locally.
|
||||
* **Fingerprint/Biometric**: Required if you want to use biometric unlock.
|
||||
* **Vibrate**: Required by the built-in keyboard (vibrate on key press)
|
||||
* **Camera**: Required for scanning OTP QR Codes
|
||||
* **Foreground service**: Required to keep the app alive for QuickUnlock (so you don't need to enter your full master password repeatedly)
|
||||
|
||||
This page has been moved to the [wiki](https://github.com/PhilippC/keepass2android/wiki/Privacy-Policy)
|
@@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
# What is Keepass2Android?
|
||||
Keepass2Android is a password manager app. It allows to store and retrieve passwords and other sensitive information in a file called "database". This database is secured with a so-called master password. The master password typically is a strong password and can be complemented with a second factor for additional security.
|
||||
The password database file can be synchronized across different devices. This works best using one of the built-in cloud storage options, but can also be performed with third-party apps. Keepass2Android is compatible with Keepass 1 and Keepass 2 on Windows and KeepassX on Linux.
|
||||
Keepass2Android is a password manager app. It allows to store and retrieve passwords and other sensitive information in a file called "database", secured with a strong key.
|
||||
The password database file can be synchronized across different devices. This works best using one of the built-in cloud storage options, but can also be performed with third-party apps. Keepass2Android is compatible with KeePass 2.x and KeepassXC on PCs as well as many other KeePass ports for a variety of platforms.
|
||||
|
||||
# Where to get it?
|
||||
Regular stable releases of Keepass2Android are available on [Google Play](https://play.google.com/store/apps/details?id=keepass2android.keepass2android).
|
||||
@@ -17,11 +17,7 @@ Beta-releases can be obtained by opting in to the [Beta testing channel](https:/
|
||||
* [Make a donation](http://philipp.crocoll.net/donate.php)
|
||||
|
||||
# How do I learn more?
|
||||
Please see the [documentation](Documentation.md).
|
||||
Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information.
|
||||
|
||||
# How do I build the project?
|
||||
If you want to build Keepass2Android, check the [build guide](Build.readme.md).
|
||||
|
||||
The project homepage is https://philipp.crocoll.net/keepass2android/index.php
|
||||
|
||||
<img src="https://github.com/PhilippC/keepass2android/actions/workflows/build.yml/badge.svg" alt="build status" /> [Build status](https://github.com/PhilippC/keepass2android/actions)
|
||||
|
@@ -1,72 +0,0 @@
|
||||
# SFTP Open/Create Database Credentials Documentation
|
||||
|
||||
## Basic Settings
|
||||
* **Host** -- the hostname or IP address of the SFTP server to connect to
|
||||
* **Port** -- the listening TCP port of the SFTP server to connect to (default: 22)
|
||||
* **Username** -- the user/account name on the SFTP server that has access to the database
|
||||
* **Initial directory** -- The path on the SFTP server that will be used as a starting point when choosing the remote database file
|
||||
|
||||
### Authentication Modes
|
||||
|
||||
#### Password
|
||||
Authenticate using a password
|
||||
|
||||
* **Password** -- the password associated with **username** used to log into the SFTP server
|
||||
|
||||
#### K2A Private/Public Key
|
||||
Authenticate using a private/public key pair that is generated internally by KP2A
|
||||
|
||||
* **SEND PUBLIC KEY...** -- Opens a standard Android "Share" screen containing the KP2A public key content. This allows for the public key to be sent via email, SMS, etc. This public key will need to be added to the SFTP server's user's "authorized keys" to allow private/public key authentication.
|
||||
|
||||
#### Custom Private Key
|
||||
Authenticate using an existing private/public key pair. Use this option instead of *K2A Private/Public Key* if you wish to use a key pair that is already set up for this **username** on the SFTP server.
|
||||
|
||||
* **Selected private key** -- a combo-box containing a list of custom private keys that KP2A knows about, and a special `[Add new...]` option.
|
||||
##### Add A New Private Key
|
||||
* Select `[Add new...]`
|
||||
* Enter a name for the new key in **New key name**
|
||||
* Enter the private key contents (text) into **New key content**. **TIP:** The easiest way to accomplish this is to open the private key file in a text editor on the device, **Select All**, **Copy** to the clipboard, and paste it into **New key content**.
|
||||
* Tap **SAVE PRIVATE KEY** to add the new key to the known list.
|
||||
|
||||
##### Use An Existing Private Key
|
||||
* To use a private key that has already been imported into KP2A, simply select it from the list of keys.
|
||||
|
||||
##### Remove An Existing Key
|
||||
* To remove a private that has been imported into KP2A, select it from the list and tap **DELETE PRIVATE KEY**.
|
||||
|
||||
A **key passphrase** can be supplied (if the key pair requires it)
|
||||
|
||||
## Advanced Settings
|
||||
* **Connection timeout seconds** -- the number of seconds to wait for a connection to the server before giving up and considering the server as unavailable/unreachable
|
||||
|
||||
### Key Algorithm Manipulation
|
||||
**NOTE: It is very rare that these fields need to be (or should be) specified. Use at your own risk!**
|
||||
|
||||
* **Key Exchange (KEX) Algorithm(s)** -- Explicitly set or modify the ordered list of Key Exchange algorithms that the SSH/SFTP client library will try to use
|
||||
* **Server Host Key Algorithm(s)** -- Explicitly set or modify the ordered list of Server Host Key algorithms that the SSH/SFTP client library will try to use
|
||||
|
||||
#### How It Works
|
||||
The SSH/SFTP client has a pre-defined ordered list of algorithm names that it will use to negotiate with the server to handle key exchange. In rare cases there are compatibility issues where Android OS has not properly implemented full support for algorithms listed. This can result in a connection failure, even if there is a suitable algorithm available (of lesser priority in the list).
|
||||
|
||||
The fields listed above allow these lists to be manipulated in the following ways to overcome/workaround such problems. The value is a comma-separated list of "algorithm spec" entries. Specs can be one of:
|
||||
|
||||
* Direct replacement of values -- Ex: `primary_alg,secondary_alg`
|
||||
* Prepend to values -- Ex: `+try_first_alg`
|
||||
* Append to values -- Ex: `try_last_alg+`
|
||||
* Remove a specific value -- Ex: `-bad_alg`
|
||||
* Remove values matching prefix -- Ex: `-bad_starting_with*`
|
||||
* Remove values matching suffix -- Ex: `-*bad_ending_with`
|
||||
* Remove values matching substring -- Ex: `-*bad_middle*`
|
||||
* Remove values matching prefix and suffix -- Ex: `-alg_begin*end`
|
||||
|
||||
For example, assume the system's KEX algorithm list is:
|
||||
`ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256`
|
||||
|
||||
These are various outcomes (user KEX field -> result):
|
||||
|
||||
* Prefix removal: `-ec*` --> `diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256`
|
||||
* Suffix removal, appending: `-*256,+first_alg,almost_last_alg+,last_alg+` --> `first_alg,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,almost_last_alg,last_alg`
|
||||
* Direct replacement: `first_alg,middle_alg,last_alg` --> `first_alg,middle_alg,last_alg`
|
||||
|
||||
## Selecting A Database
|
||||
Once all applicable fields have been entered and/or options selected, tapping **OK** will attempt to connect to the SFTP server. First time connections may pop up a dialog window asking to accept the host's authenticity (tap **yes** if the host is trusted), as well as potentially creating a new `known_hosts` file (tap **yes** to do so). If the connection is successful, a remote file browser screen will open. Navigate and select the Keepass database to open.
|
@@ -1 +0,0 @@
|
||||
theme: jekyll-theme-slate
|
Before Width: | Height: | Size: 69 KiB |
@@ -35,8 +35,6 @@ namespace KeePassLib.Cryptography
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(m_dicts.Count > 0); // Should be initialized
|
||||
|
||||
int iMaxLen = 0;
|
||||
foreach(int iLen in m_dicts.Keys)
|
||||
{
|
||||
|
@@ -86,12 +86,17 @@ namespace KeePassLib.Interfaces
|
||||
/// the current work.</returns>
|
||||
bool SetText(string strNewText, LogStatusType lsType);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the user cancelled the current work.
|
||||
/// </summary>
|
||||
/// <returns>Returns <c>true</c> if the caller should continue
|
||||
/// the current work.</returns>
|
||||
bool ContinueWork();
|
||||
void UpdateMessage(String message);
|
||||
|
||||
|
||||
void UpdateSubMessage(String submessage);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the user cancelled the current work.
|
||||
/// </summary>
|
||||
/// <returns>Returns <c>true</c> if the caller should continue
|
||||
/// the current work.</returns>
|
||||
bool ContinueWork();
|
||||
}
|
||||
|
||||
public sealed class NullStatusLogger : IStatusLogger
|
||||
@@ -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 bool ContinueWork() { return true; }
|
||||
public void UpdateMessage(string message){
|
||||
}
|
||||
|
||||
public void UpdateSubMessage(string submessage){
|
||||
}
|
||||
|
||||
public bool ContinueWork() { return true; }
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ using System.IO;
|
||||
using Android;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using KeePassLib.Serialization;
|
||||
|
||||
@@ -34,9 +35,17 @@ namespace keepass2android
|
||||
|
||||
public static void Log(string message)
|
||||
{
|
||||
if (message != null)
|
||||
Android.Util.Log.Debug("KP2A", message);
|
||||
if (LogToFile)
|
||||
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)
|
||||
{
|
||||
|
@@ -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))
|
||||
|
@@ -488,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);
|
||||
|
53
src/Kp2aBusinessLogic/BlockingOperationStarter.cs
Normal 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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -29,11 +29,19 @@ namespace keepass2android
|
||||
|
||||
}
|
||||
|
||||
|
||||
public enum MessageSeverity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
@@ -44,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; }
|
||||
@@ -88,7 +98,6 @@ namespace keepass2android
|
||||
EventHandler<DialogClickEventArgs> yesHandler,
|
||||
EventHandler<DialogClickEventArgs> noHandler,
|
||||
EventHandler<DialogClickEventArgs> cancelHandler,
|
||||
Context ctx,
|
||||
string messageSuffix = "");
|
||||
|
||||
/// <summary>
|
||||
@@ -99,13 +108,15 @@ namespace keepass2android
|
||||
EventHandler<DialogClickEventArgs> yesHandler,
|
||||
EventHandler<DialogClickEventArgs> noHandler,
|
||||
EventHandler<DialogClickEventArgs> cancelHandler,
|
||||
Context ctx,
|
||||
string messageSuffix = "");
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Handler object which can run tasks on the UI thread
|
||||
/// </summary>
|
||||
Handler UiThreadHandler { get; }
|
||||
void ShowMessage(Context ctx, int resourceId, MessageSeverity severity);
|
||||
void ShowMessage(Context ctx, string text, MessageSeverity severity);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Handler object which can run tasks on the UI thread
|
||||
/// </summary>
|
||||
Handler UiThreadHandler { get; }
|
||||
|
||||
IProgressDialog CreateProgressDialog(Context ctx);
|
||||
|
||||
@@ -125,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);
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@ using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using Java.IO;
|
||||
|
||||
using KeePass.Util;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
using File = System.IO.File;
|
||||
@@ -121,7 +121,7 @@ namespace keepass2android.Io
|
||||
var response = ex.Response as HttpWebResponse;
|
||||
if ((response != null) && (response.StatusCode == HttpStatusCode.NotFound))
|
||||
{
|
||||
throw new FileNotFoundException(ex.Message, ioc.Path, ex);
|
||||
throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(ex), ioc.Path, ex);
|
||||
}
|
||||
if (ex.Status == WebExceptionStatus.TrustFailure)
|
||||
{
|
||||
|
@@ -186,8 +186,11 @@ namespace keepass2android.Io
|
||||
Kp2aLog.Log("couldn't open from remote " + ioc.Path);
|
||||
#endif
|
||||
Kp2aLog.Log(ex.ToString());
|
||||
|
||||
_cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
|
||||
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,7 +638,9 @@ namespace keepass2android.Io
|
||||
set { _cachedStorage.IsOffline = value; }
|
||||
}
|
||||
|
||||
public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
|
||||
public bool TriggerWarningWhenFallingBackToCache { get; set; }
|
||||
|
||||
public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
|
||||
string[] permissions, Permission[] grantResults)
|
||||
{
|
||||
_cachedStorage.OnRequestPermissionsResult(fileStorageSetupActivity, requestCode, permissions, grantResults);
|
||||
|
@@ -13,6 +13,7 @@ using Keepass2android.Javafilestorage;
|
||||
#endif
|
||||
using Exception = System.Exception;
|
||||
using FileNotFoundException = Java.IO.FileNotFoundException;
|
||||
using KeePass.Util;
|
||||
|
||||
namespace keepass2android.Io
|
||||
{
|
||||
@@ -42,7 +43,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
||||
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
@@ -110,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 ??
|
||||
@@ -195,7 +201,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
||||
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
@@ -214,7 +220,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
||||
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
@@ -244,7 +250,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
||||
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
|
@@ -8,6 +8,7 @@ using Android.Content;
|
||||
using Android.OS;
|
||||
using FluentFTP;
|
||||
using FluentFTP.Exceptions;
|
||||
using KeePass.Util;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
@@ -127,7 +128,7 @@ namespace keepass2android.Io
|
||||
var ftpEx = (FtpCommandException) exception;
|
||||
|
||||
if (ftpEx.CompletionCode == "550")
|
||||
throw new FileNotFoundException(exception.Message, exception);
|
||||
throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(exception), exception);
|
||||
}
|
||||
|
||||
return exception;
|
||||
|
@@ -11,7 +11,8 @@ namespace keepass2android.Io
|
||||
public interface IOfflineSwitchable
|
||||
{
|
||||
bool IsOffline { get; set; }
|
||||
}
|
||||
bool TriggerWarningWhenFallingBackToCache { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates another IFileStorage. Allows to switch to offline mode by throwing
|
||||
@@ -21,8 +22,9 @@ namespace keepass2android.Io
|
||||
{
|
||||
private readonly IFileStorage _baseStorage;
|
||||
public bool IsOffline { get; set; }
|
||||
public bool TriggerWarningWhenFallingBackToCache { get; set; }
|
||||
|
||||
public OfflineSwitchableFileStorage(IFileStorage baseStorage)
|
||||
public OfflineSwitchableFileStorage(IFileStorage baseStorage)
|
||||
{
|
||||
_baseStorage = baseStorage;
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using Android.Content;
|
||||
using Android.Util;
|
||||
using KeePass.Util;
|
||||
using keepass2android.Io.ItemLocation;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
@@ -522,10 +523,10 @@ namespace keepass2android.Io
|
||||
{
|
||||
|
||||
if (e.IsMatch(GraphErrorCode.ItemNotFound.ToString()))
|
||||
return new FileNotFoundException(e.Message);
|
||||
return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e));
|
||||
if (e.Message.Contains("\n\n404 : ")
|
||||
) //hacky solution to check for not found. errorCode was null in my tests so I had to find a workaround.
|
||||
return new FileNotFoundException(e.Message);
|
||||
return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e));
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -1148,30 +1149,46 @@ namespace keepass2android.Io
|
||||
});
|
||||
}
|
||||
|
||||
string? driveId = parentPath.DriveId;
|
||||
if ((string.IsNullOrEmpty(driveId)) && (drives?.Any() == true))
|
||||
{
|
||||
driveId = drives.First().Id;
|
||||
}
|
||||
|
||||
|
||||
if (!CanListShares)
|
||||
return result;
|
||||
|
||||
|
||||
var sharedWithMeResponse = await client.Drives[driveId].SharedWithMe.GetAsSharedWithMeGetResponseAsync();
|
||||
|
||||
foreach (DriveItem i in sharedWithMeResponse?.Value ?? [])
|
||||
try
|
||||
{
|
||||
var oneDrive2ItemLocation = parentPath.BuildShare(i.RemoteItem.Id, i.RemoteItem.Name, i.RemoteItem.WebUrl, i.RemoteItem.ParentReference.DriveId);
|
||||
FileDescription sharedFileEntry = new FileDescription()
|
||||
string? driveId = parentPath.DriveId;
|
||||
if (string.IsNullOrEmpty(driveId))
|
||||
{
|
||||
CanWrite = true, CanRead = true, DisplayName = i.Name,
|
||||
IsDirectory = true,
|
||||
Path = oneDrive2ItemLocation.ToString()
|
||||
};
|
||||
result.Add(sharedFileEntry);
|
||||
driveId = (await client.Me.Drive.GetAsync()).Id;
|
||||
}
|
||||
if ((string.IsNullOrEmpty(driveId)) && (drives?.Any() == true))
|
||||
{
|
||||
driveId = drives.First().Id;
|
||||
}
|
||||
|
||||
var sharedWithMeResponse = await client.Drives[driveId].SharedWithMe.GetAsSharedWithMeGetResponseAsync();
|
||||
|
||||
foreach (DriveItem i in sharedWithMeResponse?.Value ?? [])
|
||||
{
|
||||
var oneDrive2ItemLocation = parentPath.BuildShare(i.RemoteItem.Id, i.RemoteItem.Name, i.RemoteItem.WebUrl, i.RemoteItem.ParentReference.DriveId);
|
||||
FileDescription sharedFileEntry = new FileDescription()
|
||||
{
|
||||
CanWrite = true,
|
||||
CanRead = true,
|
||||
DisplayName = i.Name,
|
||||
IsDirectory = (i.Folder != null) || ((i.RemoteItem != null) && (i.RemoteItem.Folder != null)),
|
||||
Path = oneDrive2ItemLocation.ToString()
|
||||
};
|
||||
result.Add(sharedFileEntry);
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logDebug("Failed to list shares: " + e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -16,20 +16,32 @@ namespace keepass2android.Io
|
||||
/// </summary>
|
||||
public class OneDriveFileStorage: IFileStorage
|
||||
{
|
||||
|
||||
public IEnumerable<string> SupportedProtocols
|
||||
public OneDriveFileStorage(IKp2aApp app)
|
||||
{
|
||||
_app = app;
|
||||
}
|
||||
|
||||
private readonly IKp2aApp _app;
|
||||
|
||||
public IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return "skydrive";
|
||||
yield return "onedrive";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Exception GetDeprecatedMessage()
|
||||
string GetDeprecatedMessage()
|
||||
{
|
||||
return
|
||||
"You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select OneDrive again.";
|
||||
}
|
||||
|
||||
private Exception GetDeprecatedException()
|
||||
{
|
||||
return new Exception(
|
||||
"You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select One Drive again.");
|
||||
GetDeprecatedMessage());
|
||||
}
|
||||
|
||||
public bool UserShouldBackup
|
||||
@@ -39,133 +51,132 @@ namespace keepass2android.Io
|
||||
|
||||
public void Delete(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public string GetFileExtension(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool RequiresCredentials(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool RequiresSetup(IOConnectionInfo ioConnection)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
public string IocToPath(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
|
||||
}
|
||||
|
||||
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
|
||||
bool alwaysReturnSuccess)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
_app.ShowMessage(activity.Activity, GetDeprecatedMessage(), MessageSeverity.Error);
|
||||
|
||||
}
|
||||
|
||||
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
|
||||
}
|
||||
|
||||
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
|
||||
}
|
||||
|
||||
public void OnResume(IFileStorageSetupActivity activity)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
|
||||
}
|
||||
|
||||
public void OnStart(IFileStorageSetupActivity activity)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
}
|
||||
|
||||
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
}
|
||||
|
||||
public string GetDisplayName(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
return "File using deprecated Microsoft API. Please update.";
|
||||
}
|
||||
|
||||
public string CreateFilePath(string parent, string newFilename)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool IsPermanentLocation(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
239
src/Kp2aBusinessLogic/OperationRunner.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -22,116 +22,152 @@ using KeePassLib.Interfaces;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
/// <summary>
|
||||
/// StatusLogger implementation which shows the progress in a progress dialog
|
||||
/// </summary>
|
||||
public class ProgressDialogStatusLogger: IStatusLogger {
|
||||
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 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) {
|
||||
_app = app;
|
||||
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);
|
||||
public void UpdateSubMessage(String submessage)
|
||||
{
|
||||
Kp2aLog.Log("status submessage: " + submessage);
|
||||
_submessage = submessage;
|
||||
if (_app != null && _progressDialog != null && _handler != null)
|
||||
{
|
||||
_handler.Post(() =>
|
||||
{
|
||||
if (!String.IsNullOrEmpty(submessage))
|
||||
{
|
||||
_progressDialog.SetMessage(_message + " (" + submessage + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
_progressDialog.SetMessage(_message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
_handler.Post(() =>
|
||||
{
|
||||
_progressDialog?.Show();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
_handler.Post(() =>
|
||||
{
|
||||
_progressDialog?.Dismiss();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateMessage(string message)
|
||||
{
|
||||
Kp2aLog.Log("status message: " + message);
|
||||
_message = message;
|
||||
if ( _app!= null && _progressDialog != null && _handler != null ) {
|
||||
_handler.Post(() => {_progressDialog.SetMessage(message); } );
|
||||
}
|
||||
}
|
||||
if (_app != null && _progressDialog != null && _handler != null)
|
||||
{
|
||||
_handler.Post(() =>
|
||||
{
|
||||
_progressDialog.SetMessage(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSubMessage(String submessage)
|
||||
{
|
||||
Kp2aLog.Log("status submessage: " + submessage);
|
||||
_submessage = submessage;
|
||||
if (_app != null && _progressDialog != null && _handler != null)
|
||||
{
|
||||
_handler.Post(() =>
|
||||
{
|
||||
if (!String.IsNullOrEmpty(submessage))
|
||||
{
|
||||
_progressDialog.SetMessage(_message + " (" + submessage + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
_progressDialog.SetMessage(_message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IStatusLogger implementation
|
||||
|
||||
public void StartLogging (string strOperation, bool bWriteOperationToLog)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void EndLogging ()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool SetProgress (uint uPercent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetText (string strNewText, LogStatusType lsType)
|
||||
{
|
||||
if (strNewText.StartsWith("KP2AKEY_"))
|
||||
{
|
||||
UiStringKey key;
|
||||
if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key))
|
||||
{
|
||||
UpdateMessage(_app.GetResourceString(key), lsType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
UpdateMessage(strNewText, lsType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateMessage(string message, LogStatusType lsType)
|
||||
{
|
||||
if (lsType == LogStatusType.AdditionalInfo)
|
||||
{
|
||||
UpdateSubMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContinueWork ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
94
src/Kp2aBusinessLogic/ProgressUiAsStatusLoggerAdapter.cs
Normal 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; } }
|
||||
}
|
@@ -4,6 +4,7 @@ using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using Java.Net;
|
||||
using KeePass.Util;
|
||||
using KeePassLib.Serialization;
|
||||
using keepass2android.Io;
|
||||
|
||||
@@ -94,15 +95,12 @@ namespace keepass2android
|
||||
}
|
||||
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
|
||||
{
|
||||
ShowToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE"));
|
||||
ShowErrorToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE"));
|
||||
}
|
||||
|
||||
if (resultCode == Result.Ok)
|
||||
{
|
||||
Kp2aLog.Log("FileSelection returned "+data.DataString);
|
||||
//TODO: don't try to extract filename if content URI
|
||||
string filename = IntentToFilename(data);
|
||||
Kp2aLog.Log("FileSelection returned filename " + filename);
|
||||
if (filename != null)
|
||||
{
|
||||
if (filename.StartsWith("file://"))
|
||||
@@ -150,7 +148,7 @@ namespace keepass2android
|
||||
|
||||
protected abstract void StartFileChooser(string path, int requestCode, bool isForSave);
|
||||
|
||||
protected abstract void ShowToast(string text);
|
||||
protected abstract void ShowErrorToast(string text);
|
||||
|
||||
protected abstract void ShowInvalidSchemeMessage(string dataString);
|
||||
|
||||
@@ -208,7 +206,7 @@ namespace keepass2android
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
ShowToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message);
|
||||
ShowErrorToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + ExceptionUtil.GetErrorMessage(e));
|
||||
ReturnCancel();
|
||||
};
|
||||
}
|
||||
|
26
src/Kp2aBusinessLogic/Utils/ExceptionUtil.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace KeePass.Util
|
||||
{
|
||||
public class ExceptionUtil
|
||||
{
|
||||
|
||||
public static string GetErrorMessage(Exception e)
|
||||
{
|
||||
string errorMessage = e.Message;
|
||||
if (e is Java.Lang.Exception javaException)
|
||||
{
|
||||
errorMessage = javaException.Message ?? errorMessage;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using KeePass.Util;
|
||||
using KeePassLib.Cryptography;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
@@ -12,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;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Finish(false, e.Message);
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -85,7 +85,7 @@ 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();
|
||||
|
||||
@@ -149,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);
|
||||
@@ -194,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());
|
||||
|
||||
|
@@ -10,6 +10,7 @@ using Com.Keepassdroid.Database.Exception;
|
||||
#endif
|
||||
using Com.Keepassdroid.Database.Save;
|
||||
using Java.Util;
|
||||
using KeePass.Util;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Cryptography;
|
||||
using KeePassLib.Cryptography.Cipher;
|
||||
@@ -82,15 +83,14 @@ namespace keepass2android
|
||||
catch (Java.IO.FileNotFoundException e)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
e.Message, e);
|
||||
ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
if (e.Message == "Invalid key!")
|
||||
throw new InvalidCompositeKeyException();
|
||||
throw new Exception(e.LocalizedMessage ??
|
||||
e.Message ??
|
||||
e.GetType().Name, e);
|
||||
throw new Exception(ExceptionUtil.GetErrorMessage(e) ??
|
||||
e.GetType().Name, e);
|
||||
}
|
||||
|
||||
HashOfLastStream = hashingStream.Hash;
|
||||
@@ -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));
|
||||
|
@@ -4,120 +4,159 @@ 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)
|
||||
{
|
||||
_context = context;
|
||||
_app = app;
|
||||
}
|
||||
|
||||
public SynchronizeCachedDatabase(IKp2aApp app, Database database, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher)
|
||||
: base(app, operationFinishedHandler)
|
||||
{
|
||||
_app = app;
|
||||
_database = database;
|
||||
_modificationWatcher = modificationWatcher;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
IOConnectionInfo ioc = _app.CurrentDb.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;
|
||||
try
|
||||
{
|
||||
IOConnectionInfo ioc = _database.Ioc;
|
||||
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
||||
if (!(fileStorage is CachingFileStorage))
|
||||
{
|
||||
throw new Exception("Cannot sync a non-cached database!");
|
||||
}
|
||||
|
||||
//download file from remote location and calculate hash:
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
|
||||
string hash;
|
||||
|
||||
MemoryStream remoteData;
|
||||
try
|
||||
{
|
||||
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
|
||||
Kp2aLog.Log("Checking for file change. Current hash = " + hash);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
|
||||
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase);
|
||||
CachingFileStorage cachingFileStorage = (CachingFileStorage)fileStorage;
|
||||
|
||||
//download file from remote location and calculate hash:
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
|
||||
string hash;
|
||||
|
||||
MemoryStream remoteData;
|
||||
try
|
||||
{
|
||||
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
|
||||
Kp2aLog.Log("Checking for file change. Current hash = " + hash);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
|
||||
cachingFileStorage.UpdateRemoteFile(ioc,
|
||||
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
Kp2aLog.Log("Checking for file change: file not found");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//check if remote file was modified:
|
||||
//check if remote file was modified:
|
||||
var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc);
|
||||
Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash);
|
||||
if (baseVersionHash != hash)
|
||||
{
|
||||
//remote file is modified
|
||||
if (cachingFileStorage.HasLocalChanges(ioc))
|
||||
{
|
||||
//conflict! need to merge
|
||||
_saveDb = new SaveDb(_context, _app, new ActionOnFinish(ActiveActivity, (success, result, activity) =>
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
Finish(false, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
}
|
||||
_saveDb = null;
|
||||
}), _app.CurrentDb, false, remoteData);
|
||||
_saveDb.Run();
|
||||
if (baseVersionHash != hash)
|
||||
{
|
||||
//remote file is modified
|
||||
if (cachingFileStorage.HasLocalChanges(ioc))
|
||||
{
|
||||
//conflict! need to merge
|
||||
var _saveDb = new SaveDb(_app, new ActionOnOperationFinished(_app,
|
||||
(success, result, activity) =>
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
Finish(false, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
}
|
||||
}), _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));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//remote file is unmodified
|
||||
if (cachingFileStorage.HasLocalChanges(ioc))
|
||||
{
|
||||
//but we have local changes -> upload:
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
|
||||
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||
StatusLogger.UpdateSubMessage("");
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
}
|
||||
else
|
||||
{
|
||||
//files are in sync: just set the result
|
||||
Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
|
||||
}
|
||||
}
|
||||
}
|
||||
_app.MarkAllGroupsAsDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
//only the remote file was modified -> reload database.
|
||||
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
|
||||
{
|
||||
//remote file is unmodified
|
||||
if (cachingFileStorage.HasLocalChanges(ioc))
|
||||
{
|
||||
//but we have local changes -> upload:
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
|
||||
cachingFileStorage.UpdateRemoteFile(ioc,
|
||||
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||
StatusLogger.UpdateSubMessage("");
|
||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||
}
|
||||
else
|
||||
{
|
||||
//files are in sync: just set the result
|
||||
Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Java.Lang.InterruptedException e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
//no Finish()
|
||||
}
|
||||
catch (Java.IO.InterruptedIOException e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
//no Finish()
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Finish(false, e.Message);
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void JoinWorkerThread()
|
||||
{
|
||||
if (_saveDb != null)
|
||||
_saveDb.JoinWorkerThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
_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;
|
||||
|
||||
|
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
@@ -26,27 +26,24 @@ 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;
|
||||
_ioc = ioc;
|
||||
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;
|
||||
_ioc = ioc;
|
||||
_dontSave = dontSave;
|
||||
_app = app;
|
||||
_key = key;
|
||||
@@ -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();
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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; }
|
||||
|
||||
|
||||
}
|
@@ -21,59 +21,88 @@ 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)
|
||||
{
|
||||
_app = app;
|
||||
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||
_app = app;
|
||||
_ioc = ioc;
|
||||
_databaseData = databaseData;
|
||||
_compositeKey = compositeKey;
|
||||
_keyfileOrProvider = keyfileOrProvider;
|
||||
_updateLastUsageTimestamp = updateLastUsageTimestamp;
|
||||
_makeCurrent = makeCurrent;
|
||||
|
||||
|
||||
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
|
||||
}
|
||||
_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()
|
||||
public override void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
//make sure the file data is stored in the recent files list even if loading fails
|
||||
SaveFileData(_ioc, _keyfileOrProvider);
|
||||
SaveFileData(_ioc, _keyfileOrProvider);
|
||||
|
||||
|
||||
var fileStorage = _app.GetFileStorage(_ioc);
|
||||
|
||||
RequiresSubsequentSync = false;
|
||||
|
||||
|
||||
StatusLogger.UpdateMessage(UiStringKey.loading_database);
|
||||
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)
|
||||
databaseStream = preloadedMemoryStream;
|
||||
else
|
||||
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);
|
||||
@@ -81,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);
|
||||
|
||||
|
||||
@@ -103,10 +137,10 @@ namespace keepass2android
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
string message = e.Message;
|
||||
string message = ExceptionUtil.GetErrorMessage(e);
|
||||
foreach (var innerException in e.InnerExceptions)
|
||||
{
|
||||
message = innerException.Message;
|
||||
message = ExceptionUtil.GetErrorMessage(innerException);
|
||||
// Override the message shown with the last (hopefully most recent) inner exception
|
||||
Kp2aLog.LogUnexpectedError(innerException);
|
||||
}
|
||||
@@ -116,21 +150,29 @@ namespace keepass2android
|
||||
catch (DuplicateUuidsException e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
|
||||
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Java.Lang.InterruptedException)
|
||||
{
|
||||
Kp2aLog.Log("Load interrupted");
|
||||
//close without Finish()
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!(e is InvalidCompositeKeyException))
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (e.Message ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception);
|
||||
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (ExceptionUtil.GetErrorMessage(e) ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
public bool RequiresSubsequentSync { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the exception which was thrown during execution (if any)
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
@@ -145,15 +187,21 @@ namespace keepass2android
|
||||
workingCopy.Seek(0, SeekOrigin.Begin);
|
||||
//reset stream if we need to reuse it later:
|
||||
databaseStream.Seek(0, SeekOrigin.Begin);
|
||||
if (!StatusLogger.ContinueWork())
|
||||
{
|
||||
throw new Java.Lang.InterruptedException();
|
||||
}
|
||||
|
||||
//now let's go:
|
||||
try
|
||||
{
|
||||
Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent);
|
||||
Kp2aLog.Log("LoadDB OK");
|
||||
|
||||
try
|
||||
{
|
||||
Database newDb =
|
||||
_app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent, _modificationWatcher);
|
||||
Kp2aLog.Log("LoadDB OK");
|
||||
|
||||
Finish(true, _format.SuccessMessage);
|
||||
return newDb;
|
||||
}
|
||||
return newDb;
|
||||
}
|
||||
catch (OldFormatException)
|
||||
{
|
||||
_format = new KdbDatabaseFormat(_app);
|
||||
|
@@ -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;
|
||||
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();
|
||||
|
@@ -1,154 +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;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using Google.Android.Material.Dialog;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
public abstract class OnFinish
|
||||
{
|
||||
protected bool Success;
|
||||
protected String Message;
|
||||
protected Exception Exception;
|
||||
|
||||
protected bool ImportantMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
protected OnFinish BaseOnFinish;
|
||||
protected Handler Handler;
|
||||
private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired
|
||||
private Activity _activeActivity, _previouslyActiveActivity;
|
||||
|
||||
|
||||
public ProgressDialogStatusLogger StatusLogger
|
||||
{
|
||||
get { return _statusLogger; }
|
||||
set { _statusLogger = value; }
|
||||
}
|
||||
|
||||
public Activity ActiveActivity
|
||||
{
|
||||
get { return _activeActivity; }
|
||||
set
|
||||
{
|
||||
if (_activeActivity != null && _activeActivity != _previouslyActiveActivity)
|
||||
{
|
||||
_previouslyActiveActivity = _activeActivity;
|
||||
|
||||
}
|
||||
_activeActivity = value;
|
||||
if (BaseOnFinish != null)
|
||||
{
|
||||
BaseOnFinish.ActiveActivity = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Activity PreviouslyActiveActivity
|
||||
{
|
||||
get { return _previouslyActiveActivity; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected OnFinish(Activity activeActivity, Handler handler)
|
||||
{
|
||||
ActiveActivity = activeActivity;
|
||||
BaseOnFinish = null;
|
||||
Handler = handler;
|
||||
|
||||
}
|
||||
|
||||
protected OnFinish(Activity activeActivity, OnFinish finish, Handler handler)
|
||||
{
|
||||
ActiveActivity = activeActivity;
|
||||
BaseOnFinish = finish;
|
||||
Handler = handler;
|
||||
}
|
||||
|
||||
protected OnFinish(Activity activeActivity, OnFinish finish)
|
||||
{
|
||||
ActiveActivity = activeActivity;
|
||||
BaseOnFinish = finish;
|
||||
Handler = null;
|
||||
}
|
||||
|
||||
public void SetResult(bool success, string message, bool importantMessage, Exception exception) {
|
||||
Success = success;
|
||||
Message = message;
|
||||
ImportantMessage = importantMessage;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
|
||||
public void SetResult(bool success) {
|
||||
Success = success;
|
||||
}
|
||||
|
||||
public virtual void Run() {
|
||||
if (BaseOnFinish == null) return;
|
||||
// Pass on result on call finish
|
||||
BaseOnFinish.SetResult(Success, Message, ImportantMessage, Exception);
|
||||
|
||||
if ( Handler != null ) {
|
||||
Handler.Post(BaseOnFinish.Run);
|
||||
} else {
|
||||
BaseOnFinish.Run();
|
||||
}
|
||||
}
|
||||
|
||||
protected void DisplayMessage(Context ctx) {
|
||||
DisplayMessage(ctx, Message, ImportantMessage);
|
||||
}
|
||||
|
||||
public static void DisplayMessage(Context ctx, string message, bool makeDialog)
|
||||
{
|
||||
if ( !String.IsNullOrEmpty(message) ) {
|
||||
Kp2aLog.Log("OnFinish message: " + message);
|
||||
if (makeDialog && ctx != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
|
||||
|
||||
builder.SetMessage(message)
|
||||
.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss())
|
||||
.Show();
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Toast.MakeText(ctx, message, ToastLength.Long).Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
Toast.MakeText(ctx ?? Application.Context, message, ToastLength.Long).Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
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;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using Google.Android.Material.Dialog;
|
||||
using KeePassLib.Interfaces;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
public interface IActiveContextProvider
|
||||
{
|
||||
Context ActiveContext { get; }
|
||||
}
|
||||
|
||||
public abstract class OnOperationFinishedHandler
|
||||
{
|
||||
protected bool Success;
|
||||
protected String Message;
|
||||
protected Exception Exception;
|
||||
|
||||
protected bool ImportantMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
protected Context ActiveContext
|
||||
{
|
||||
get
|
||||
{
|
||||
return _activeContextProvider?.ActiveContext;
|
||||
}
|
||||
}
|
||||
|
||||
protected OnOperationFinishedHandler NextOnOperationFinishedHandler;
|
||||
protected Handler Handler;
|
||||
private IKp2aStatusLogger _statusLogger = new Kp2aNullStatusLogger(); //default: no logging but not null -> can be used whenever desired
|
||||
private readonly IActiveContextProvider _activeContextProvider;
|
||||
|
||||
public IKp2aStatusLogger StatusLogger
|
||||
{
|
||||
get { return _statusLogger; }
|
||||
set { _statusLogger = value; }
|
||||
} protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, Handler handler)
|
||||
{
|
||||
_activeContextProvider = activeContextProvider;
|
||||
NextOnOperationFinishedHandler = null;
|
||||
Handler = handler;
|
||||
}
|
||||
|
||||
protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, OnOperationFinishedHandler operationFinishedHandler, Handler handler)
|
||||
{
|
||||
_activeContextProvider = activeContextProvider;
|
||||
NextOnOperationFinishedHandler = operationFinishedHandler;
|
||||
Handler = handler;
|
||||
}
|
||||
|
||||
protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, OnOperationFinishedHandler operationFinishedHandler)
|
||||
{
|
||||
_activeContextProvider = activeContextProvider;
|
||||
NextOnOperationFinishedHandler = operationFinishedHandler;
|
||||
Handler = null;
|
||||
}
|
||||
|
||||
public void SetResult(bool success, string message, bool importantMessage, Exception exception) {
|
||||
Success = success;
|
||||
Message = message;
|
||||
ImportantMessage = importantMessage;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
|
||||
public void SetResult(bool success) {
|
||||
Success = success;
|
||||
}
|
||||
|
||||
public virtual void Run() {
|
||||
if (NextOnOperationFinishedHandler == null) return;
|
||||
// Pass on result on call finish
|
||||
NextOnOperationFinishedHandler.SetResult(Success, Message, ImportantMessage, Exception);
|
||||
|
||||
var handler = Handler ?? NextOnOperationFinishedHandler.Handler ?? null;
|
||||
|
||||
if (handler != null ) {
|
||||
handler.Post(() =>
|
||||
{
|
||||
NextOnOperationFinishedHandler.Run();
|
||||
});
|
||||
} else {
|
||||
NextOnOperationFinishedHandler.Run();
|
||||
}
|
||||
}
|
||||
|
||||
protected void DisplayMessage(Context ctx) {
|
||||
DisplayMessage(ctx, Message, ImportantMessage);
|
||||
}
|
||||
|
||||
public static void DisplayMessage(Context ctx, string message, bool makeDialog)
|
||||
{
|
||||
if ( !String.IsNullOrEmpty(message) ) {
|
||||
Kp2aLog.Log("OnOperationFinishedHandler message: " + message);
|
||||
if (makeDialog && ctx != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
|
||||
|
||||
builder.SetMessage(message)
|
||||
.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss())
|
||||
.Show();
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Toast.MakeText(ctx, message, ToastLength.Long).Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
Toast.MakeText(ctx ?? Application.Context, message, ToastLength.Long).Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -29,57 +29,70 @@ using KeePassLib.Utility;
|
||||
using keepass2android.Io;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
using Exception = System.Exception;
|
||||
using KeePass.Util;
|
||||
using Thread = System.Threading.Thread;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Save the database. If the file has changed, ask the user if he wants to overwrite or sync.
|
||||
/// </summary>
|
||||
|
||||
public class SaveDb : RunnableOnFinish {
|
||||
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)
|
||||
{
|
||||
_db = db;
|
||||
_ctx = ctx;
|
||||
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||
_db = db;
|
||||
_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;
|
||||
_app = app;
|
||||
|
||||
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||
_app = app;
|
||||
_db = db;
|
||||
_dontSave = false;
|
||||
}
|
||||
SyncInBackground = _app.SyncInBackgroundPreference;
|
||||
}
|
||||
|
||||
public bool ShowDatabaseIocInStatus { get; set; }
|
||||
|
||||
@@ -102,29 +115,42 @@ namespace keepass2android
|
||||
if (ShowDatabaseIocInStatus)
|
||||
message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
|
||||
|
||||
StatusLogger.UpdateMessage(message);
|
||||
|
||||
IOConnectionInfo ioc = _db.Ioc;
|
||||
if (!DoNotSetStatusLoggerMessage)
|
||||
{
|
||||
StatusLogger.UpdateMessage(message);
|
||||
}
|
||||
|
||||
IOConnectionInfo ioc = _db.Ioc;
|
||||
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
||||
|
||||
if (_streamForOrigFile == null)
|
||||
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)
|
||||
{
|
||||
@@ -157,15 +183,14 @@ namespace keepass2android
|
||||
RunInWorkerThread(() =>
|
||||
{
|
||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||
Finish(true);
|
||||
FinishWithSuccess();
|
||||
});
|
||||
},
|
||||
//cancel
|
||||
(sender, args) =>
|
||||
{
|
||||
RunInWorkerThread(() => Finish(false));
|
||||
},
|
||||
_ctx
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,7 +198,7 @@ namespace keepass2android
|
||||
else
|
||||
{
|
||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||
Finish(true);
|
||||
FinishWithSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -187,28 +212,73 @@ namespace keepass2android
|
||||
}
|
||||
*/
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Finish(false, e.Message);
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
try
|
||||
{
|
||||
_modificationWatcher.BeforeModifyDatabases();
|
||||
}
|
||||
catch (Java.Lang.InterruptedException)
|
||||
{
|
||||
// leave without Finish()
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
MergeIn(fileStorage, ioc);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_modificationWatcher.AfterModifyDatabases();
|
||||
|
||||
}
|
||||
|
||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||
_db.UpdateGlobals();
|
||||
Finish(true);
|
||||
new Handler(Looper.MainLooper).Post(() =>
|
||||
{
|
||||
_db.UpdateGlobals();
|
||||
});
|
||||
|
||||
FinishWithSuccess();
|
||||
}
|
||||
|
||||
|
||||
private void RunInWorkerThread(Action runHandler)
|
||||
{
|
||||
try
|
||||
@@ -222,8 +292,8 @@ namespace keepass2android
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Kp2aLog.Log("Error in worker thread of SaveDb: " + e);
|
||||
Finish(false, e.Message);
|
||||
Kp2aLog.Log("Error in worker thread of SaveDb: " + ExceptionUtil.GetErrorMessage(e));
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
});
|
||||
@@ -233,7 +303,7 @@ namespace keepass2android
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Kp2aLog.Log("Error starting worker thread of SaveDb: "+e);
|
||||
Finish(false, e.Message);
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -281,7 +351,7 @@ namespace keepass2android
|
||||
private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc)
|
||||
{
|
||||
StatusLogger.UpdateSubMessage("");
|
||||
_db.SaveData();
|
||||
_db.SaveData(fileStorage);
|
||||
_db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
_app = app;
|
||||
|
||||
_onFinishToRun = new AfterUpdate(ctx, oldE, newE, app, finish);
|
||||
public UpdateEntry(IKp2aApp app, PwEntry oldE, PwEntry newE, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) {
|
||||
_app = app;
|
||||
|
||||
_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;
|
||||
|
25
src/build-scripts/build-java.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
#unset ANDROID_NDK_HOME ANDROID_NDK
|
||||
|
||||
pushd ../java/
|
||||
|
||||
pushd JavaFileStorageTest-AS
|
||||
./gradlew assemble
|
||||
popd
|
||||
|
||||
pushd KP2ASoftkeyboard_AS
|
||||
./gradlew assemble
|
||||
popd
|
||||
|
||||
pushd Keepass2AndroidPluginSDK2
|
||||
./gradlew assemble
|
||||
popd
|
||||
|
||||
pushd KP2AKdbLibrary
|
||||
./gradlew assemble
|
||||
popd
|
||||
|
||||
popd
|
6
src/build-scripts/build-native.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
pushd ../java/argon2
|
||||
ndk-build
|
||||
popd
|
29
src/build-scripts/linux-build.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## Setup build environment
|
||||
* install Android SDK
|
||||
* install Android NDK
|
||||
* install dotnet8
|
||||
|
||||
```
|
||||
|
||||
#from https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-8.0.407-linux-x64-binaries
|
||||
wget https://download.visualstudio.microsoft.com/download/pr/9d07577e-f7bc-4d60-838d-f79c50b5c11a/459ef339396783db369e0432d6dc3d7e/dotnet-sdk-8.0.407-linux-x64.tar.gz
|
||||
mkdir -p $HOME/dotnet && tar zxf dotnet-sdk-8.0.407-linux-x64.tar.gz -C $HOME/dotnet
|
||||
export DOTNET_ROOT=$HOME/dotnet
|
||||
export PATH=$PATH:$HOME/dotnet
|
||||
|
||||
```
|
||||
|
||||
## Build Keepass2Android
|
||||
|
||||
```
|
||||
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
|
||||
dotnet restore
|
||||
dotnet build
|
||||
```
|
@@ -113,4 +113,15 @@ extends Activity implements JavaFileStorage.FileStorageSetupActivity {
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
MainActivity.storageToTest.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -147,6 +147,7 @@ import java.util.List;
|
||||
//import keepass2android.javafilestorage.DropboxCloudRailStorage;
|
||||
import keepass2android.javafilestorage.DropboxV2Storage;
|
||||
import keepass2android.javafilestorage.GoogleDriveAppDataFileStorage;
|
||||
import keepass2android.javafilestorage.GoogleDriveFullFileStorage;
|
||||
import keepass2android.javafilestorage.ICertificateErrorHandler;
|
||||
import keepass2android.javafilestorage.JavaFileStorage;
|
||||
import keepass2android.javafilestorage.JavaFileStorage.FileEntry;
|
||||
@@ -547,7 +548,7 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
|
||||
|
||||
//storageToTest = new GoogleDriveAppDataFileStorage();
|
||||
storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
|
||||
/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
|
||||
@Override
|
||||
public boolean onValidationError(String error) {
|
||||
return false;
|
||||
@@ -558,10 +559,11 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
*/
|
||||
//storageToTest = new DropboxV2Storage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
|
||||
//storageToTest = new DropboxFileStorage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
|
||||
//storageToTest = new DropboxAppFolderFileStorage(ctx,"ax0268uydp1ya57", "3s86datjhkihwyc", true);
|
||||
storageToTest = new GoogleDriveFullFileStorage();
|
||||
|
||||
|
||||
return storageToTest;
|
||||
@@ -580,6 +582,8 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
getMenuInflater().inflate(R.menu.main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
|
@@ -64,8 +64,6 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
|
||||
Log.d("KP2A_FC_P", "onCreate");
|
||||
|
||||
BaseFileProviderUtils.registerProviderInfo(_ID,
|
||||
getAuthority());
|
||||
|
||||
@@ -222,12 +220,12 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
try
|
||||
{
|
||||
checkConnection(uri);
|
||||
Log.d("KP2A_FC_P", "checking connection for " + uri + " ok.");
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P", "checking connection for " + uri + " ok.");
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.d("KP2A_FC_P","Check connection failed with: " + e.toString());
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P","Check connection failed with: " + e.toString());
|
||||
|
||||
MatrixCursor matrixCursor = new MatrixCursor(BaseFileProviderUtils.CONNECTION_CHECK_CURSOR_COLUMNS);
|
||||
RowBuilder newRow = matrixCursor.newRow();
|
||||
@@ -255,7 +253,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Log.d("KP2A_FC_P","File not found. Ignore.");
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P","File not found. Ignore.");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -276,8 +274,8 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
MatrixCursor matrixCursor = null;
|
||||
|
||||
String lastPathSegment = uri.getLastPathSegment();
|
||||
|
||||
Log.d("KP2A_FC_P", "lastPathSegment:" + lastPathSegment);
|
||||
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P", "lastPathSegment:" + lastPathSegment);
|
||||
|
||||
if (BaseFile.CMD_CANCEL.equals(lastPathSegment)) {
|
||||
int taskId = ProviderUtils.getIntQueryParam(uri,
|
||||
@@ -361,7 +359,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
|
||||
} else if (BaseFile.CMD_CHECK_CONNECTION.equals(lastPathSegment))
|
||||
{
|
||||
Log.d("KP2A_FC_P","Check connection...");
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P","Check connection...");
|
||||
return getCheckConnectionCursor(uri);
|
||||
}
|
||||
|
||||
@@ -470,7 +468,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
String displayName = getFileEntryCached(dirName).displayName;
|
||||
newRow.add(displayName);
|
||||
|
||||
Log.d(CLASSNAME, "Returning name " + displayName+" for " +dirName);
|
||||
if (Utils.doLog()) Log.d(CLASSNAME, "Returning name " + displayName+" for " +dirName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,7 +688,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.d("KP2A_FC_P", "sortFiles() >> "+e);
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P", "sortFiles() >> "+e);
|
||||
throw e;
|
||||
}
|
||||
}// sortFiles()
|
||||
@@ -777,14 +775,14 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
path = removeTrailingSlash(path);
|
||||
if (path.indexOf("://") == -1)
|
||||
{
|
||||
Log.d("KP2A_FC_P", "invalid path: " + path);
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P", "invalid path: " + path);
|
||||
return null;
|
||||
}
|
||||
String pathWithoutProtocol = path.substring(path.indexOf("://") + 3);
|
||||
int lastSlashPos = path.lastIndexOf("/");
|
||||
if (pathWithoutProtocol.indexOf("/") == -1)
|
||||
{
|
||||
Log.d("KP2A_FC_P", "parent of " + path + " is null");
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P", "parent of " + path + " is null");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
@@ -793,7 +791,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
if (params != null) {
|
||||
parent += params;
|
||||
}
|
||||
Log.d("KP2A_FC_P", "parent of " + path +" is " + parent);
|
||||
if (Utils.doLog()) Log.d("KP2A_FC_P", "parent of " + path +" is " + parent);
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
@@ -55,4 +55,19 @@
|
||||
<string name="afc_title_size">Dimensiune</string>
|
||||
<string name="afc_title_sort_by">Sortează după…</string>
|
||||
<string name="afc_yesterday">Ieri</string>
|
||||
<plurals name="afc_title_choose_directories">
|
||||
<item quantity="one">Alege dosarul…</item>
|
||||
<item quantity="few">Alege dosarele…</item>
|
||||
<item quantity="other">Alege dosarele…</item>
|
||||
</plurals>
|
||||
<plurals name="afc_title_choose_files">
|
||||
<item quantity="one">Alege fișierul…</item>
|
||||
<item quantity="few">Alege fișierele…</item>
|
||||
<item quantity="other">Alege fișierele…</item>
|
||||
</plurals>
|
||||
<plurals name="afc_title_choose_files_directories">
|
||||
<item quantity="one">Alege fișierul/dosarul…</item>
|
||||
<item quantity="few">Alege fișierele/dosarele…</item>
|
||||
<item quantity="other">Alege fișierele/dosarele…</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
@@ -59,7 +59,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (ActivityNotFoundException)
|
||||
{
|
||||
Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -71,7 +71,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (ActivityNotFoundException)
|
||||
{
|
||||
Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error);
|
||||
}
|
||||
};
|
||||
FindViewById(Resource.Id.translate).Click += delegate
|
||||
@@ -82,7 +82,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (ActivityNotFoundException)
|
||||
{
|
||||
Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error);
|
||||
}
|
||||
}; FindViewById(Resource.Id.donate).Click += delegate
|
||||
{
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -183,7 +183,7 @@ namespace keepass2android
|
||||
// Verify that a password or keyfile is set
|
||||
if (password.Length == 0 && !keyfileCheckbox.Checked)
|
||||
{
|
||||
Toast.MakeText(this, Resource.String.error_nopass, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(this, Resource.String.error_nopass, MessageSeverity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,16 +207,15 @@ namespace keepass2android
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Toast.MakeText(this, Resource.String.error_adding_keyfile, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(this, Resource.String.error_adding_keyfile, MessageSeverity.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -235,7 +234,7 @@ namespace keepass2android
|
||||
if (! pass.Equals(confpass))
|
||||
{
|
||||
// Passwords do not match
|
||||
Toast.MakeText(this, Resource.String.error_pass_match, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(this, Resource.String.error_pass_match, MessageSeverity.Error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -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,14 +402,14 @@ 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;
|
||||
_activity = activity;
|
||||
_ioc = ioc;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Finish(false, e.Message);
|
||||
Finish(false, Util.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -41,7 +41,7 @@ namespace keepass2android
|
||||
string requestedUrl = Intent.GetStringExtra(ChooseForAutofillActivityBase.ExtraQueryString);
|
||||
if (requestedUrl == null)
|
||||
{
|
||||
Toast.MakeText(this, "Cannot execute query for null.", ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(this, "Cannot execute query for null.", MessageSeverity.Error);
|
||||
RestartApp();
|
||||
return;
|
||||
}
|
||||
|
@@ -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)
|
||||
Toast.MakeText(activity, message, ToastLength.Long).Show();
|
||||
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";
|
||||
@@ -107,8 +109,47 @@ namespace keepass2android
|
||||
|
||||
public const int requestCodeBinaryFilename = 42376;
|
||||
public const int requestCodeSelFileStorageForWriteAttachment = 42377;
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -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,7 +542,13 @@ 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) =>
|
||||
{
|
||||
activity.SetResult(KeePass.ExitRefresh);
|
||||
activity.Finish();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -767,9 +818,9 @@ namespace keepass2android
|
||||
|
||||
if (parent == null || (parent.Exists() && !parent.IsDirectory))
|
||||
{
|
||||
Toast.MakeText(this,
|
||||
App.Kp2a.ShowMessage(this,
|
||||
Resource.String.error_invalid_path,
|
||||
ToastLength.Long).Show();
|
||||
MessageSeverity.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -778,9 +829,9 @@ namespace keepass2android
|
||||
// Create parent directory
|
||||
if (!parent.Mkdirs())
|
||||
{
|
||||
Toast.MakeText(this,
|
||||
App.Kp2a.ShowMessage(this,
|
||||
Resource.String.error_could_not_create_parent,
|
||||
ToastLength.Long).Show();
|
||||
MessageSeverity.Error);
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -794,18 +845,18 @@ namespace keepass2android
|
||||
}
|
||||
catch (Exception exWrite)
|
||||
{
|
||||
Toast.MakeText(this,
|
||||
App.Kp2a.ShowMessage(this,
|
||||
GetString(Resource.String.SaveAttachment_Failed, new Java.Lang.Object[] {filename})
|
||||
+ exWrite.Message, ToastLength.Long).Show();
|
||||
+ Util.GetErrorMessage(exWrite), MessageSeverity.Error);
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemUtil.ZeroByteArray(pbData);
|
||||
}
|
||||
Toast.MakeText(this,
|
||||
App.Kp2a.ShowMessage(this,
|
||||
GetString(Resource.String.SaveAttachment_doneMessage, new Java.Lang.Object[] {filename}),
|
||||
ToastLength.Short).Show();
|
||||
MessageSeverity.Info);
|
||||
return Uri.Parse("content://" + AttachmentContentProvider.Authority + "/"
|
||||
+ filename);
|
||||
}
|
||||
@@ -838,7 +889,7 @@ namespace keepass2android
|
||||
catch (ActivityNotFoundException)
|
||||
{
|
||||
//ignore
|
||||
Toast.MakeText(this, "Couldn't open file", ToastLength.Short).Show();
|
||||
App.Kp2a.ShowMessage(this, "Couldn't open file", MessageSeverity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,7 +1129,9 @@ namespace keepass2android
|
||||
UnregisterReceiver(_pluginActionReceiver);
|
||||
if (_pluginFieldReceiver != null)
|
||||
UnregisterReceiver(_pluginFieldReceiver);
|
||||
base.OnDestroy();
|
||||
if (_dataUpdatedIntentReceiver != null)
|
||||
UnregisterReceiver(_dataUpdatedIntentReceiver);
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
private void NotifyPluginsOnClose()
|
||||
@@ -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;
|
||||
@@ -1305,7 +1358,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Finish(false, ex.Message);
|
||||
Finish(false, Util.GetErrorMessage(ex));
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -1558,7 +1612,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (ActivityNotFoundException)
|
||||
{
|
||||
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(this, Resource.String.no_url_handler, MessageSeverity.Error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1603,5 +1657,7 @@ namespace keepass2android
|
||||
imageViewerIntent.PutExtra("EntryKey", key);
|
||||
StartActivity(imageViewerIntent);
|
||||
}
|
||||
}
|
||||
|
||||
public IProgressUi? ProgressUi => FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container);
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
@@ -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)
|
||||
Toast.MakeText(activity, message, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||
else
|
||||
Toast.MakeText(activity, _activity.GetString(Resource.String.export_database_successful), ToastLength.Long).Show();
|
||||
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;
|
||||
@@ -140,7 +140,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Finish(false, ex.Message);
|
||||
Finish(false, Util.GetErrorMessage(ex));
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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,11 +103,11 @@ 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)
|
||||
{
|
||||
Toast.MakeText(activity, messageOrFilename, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(activity, messageOrFilename, MessageSeverity.Error);
|
||||
return;
|
||||
}
|
||||
SaveFile(new IOConnectionInfo { Path = FileSelectHelper.ConvertFilenameToIocPath(messageOrFilename) });
|
||||
@@ -115,7 +115,7 @@ namespace keepass2android
|
||||
|
||||
}), filename);
|
||||
|
||||
new ProgressTask(App.Kp2a, _activity, task).Run();
|
||||
new BlockingOperationStarter(App.Kp2a, task).Run();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -115,6 +115,7 @@ namespace keepass2android
|
||||
string keyContent = keyContentTxt.Text;
|
||||
|
||||
string toastMsg = null;
|
||||
MessageSeverity severity = MessageSeverity.Info;
|
||||
if (!string.IsNullOrEmpty(keyName) && !string.IsNullOrEmpty(keyContent))
|
||||
{
|
||||
try
|
||||
@@ -127,8 +128,10 @@ namespace keepass2android
|
||||
catch (Exception e)
|
||||
{
|
||||
toastMsg = ctx.GetString(Resource.String.private_key_save_failed,
|
||||
new Java.Lang.Object[] { e.Message });
|
||||
}
|
||||
new Java.Lang.Object[] { Util.GetErrorMessage(e)});
|
||||
severity = MessageSeverity.Error;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -136,7 +139,7 @@ namespace keepass2android
|
||||
}
|
||||
|
||||
if (toastMsg!= null) {
|
||||
Toast.MakeText(_activity, toastMsg, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(_activity, toastMsg, severity);
|
||||
}
|
||||
|
||||
UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx);
|
||||
@@ -153,7 +156,7 @@ namespace keepass2android
|
||||
|
||||
int msgId = deleted ? Resource.String.private_key_delete : Resource.String.private_key_delete_failed;
|
||||
string msg = ctx.GetString(msgId, new Java.Lang.Object[] { keyName });
|
||||
Toast.MakeText(_activity, msg, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(_activity, msg, deleted ? MessageSeverity.Info :MessageSeverity.Error);
|
||||
|
||||
UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx);
|
||||
keySpinner.SetSelection(SftpKeySpinnerCreateNewIdx);
|
||||
@@ -581,9 +584,9 @@ namespace keepass2android
|
||||
// Make sure file name exists
|
||||
if (filename.Length == 0)
|
||||
{
|
||||
Toast.MakeText(_activity,
|
||||
App.Kp2a.ShowMessage(_activity,
|
||||
Resource.String.error_filename_required,
|
||||
ToastLength.Long).Show();
|
||||
MessageSeverity.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -604,9 +607,9 @@ namespace keepass2android
|
||||
}
|
||||
catch (NoFileStorageFoundException)
|
||||
{
|
||||
Toast.MakeText(_activity,
|
||||
App.Kp2a.ShowMessage(_activity,
|
||||
"Unexpected scheme in "+filename,
|
||||
ToastLength.Long).Show();
|
||||
MessageSeverity.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -620,9 +623,9 @@ namespace keepass2android
|
||||
|
||||
if (parent == null || (parent.Exists() && !parent.IsDirectory))
|
||||
{
|
||||
Toast.MakeText(_activity,
|
||||
App.Kp2a.ShowMessage(_activity,
|
||||
Resource.String.error_invalid_path,
|
||||
ToastLength.Long).Show();
|
||||
MessageSeverity.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -631,9 +634,9 @@ namespace keepass2android
|
||||
// Create parent dircetory
|
||||
if (!parent.Mkdirs())
|
||||
{
|
||||
Toast.MakeText(_activity,
|
||||
App.Kp2a.ShowMessage(_activity,
|
||||
Resource.String.error_could_not_create_parent,
|
||||
ToastLength.Long).Show();
|
||||
MessageSeverity.Error);
|
||||
return false;
|
||||
|
||||
}
|
||||
@@ -643,11 +646,11 @@ namespace keepass2android
|
||||
}
|
||||
catch (Java.IO.IOException ex)
|
||||
{
|
||||
Toast.MakeText(
|
||||
App.Kp2a.ShowMessage(
|
||||
_activity,
|
||||
_activity.GetText(Resource.String.error_file_not_create) + " "
|
||||
+ ex.LocalizedMessage,
|
||||
ToastLength.Long).Show();
|
||||
MessageSeverity.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -664,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)
|
||||
@@ -700,7 +703,7 @@ namespace keepass2android
|
||||
_activity.StartActivityForResult(i, _requestCode);
|
||||
|
||||
#else
|
||||
Toast.MakeText(LocaleManager.LocalizedAppContext, "File chooser is excluded!", ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext, "File chooser is excluded!", MessageSeverity.Error);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@@ -778,11 +781,11 @@ 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)
|
||||
{
|
||||
Toast.MakeText(newActivity, messageOrFilename, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(newActivity, messageOrFilename, MessageSeverity.Error);
|
||||
return;
|
||||
}
|
||||
var ioc = new IOConnectionInfo { Path = ConvertFilenameToIocPath(messageOrFilename) };
|
||||
@@ -790,7 +793,7 @@ namespace keepass2android
|
||||
|
||||
}), filename);
|
||||
|
||||
new ProgressTask(App.Kp2a, activity, task).Run();
|
||||
new BlockingOperationStarter(App.Kp2a, task).Run();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -251,7 +251,7 @@ namespace keepass2android
|
||||
catch (Exception e)
|
||||
{
|
||||
CheckCurrentRadioButton();
|
||||
Toast.MakeText(this, e.ToString(), ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(this, e.ToString(), MessageSeverity.Error);
|
||||
FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible;
|
||||
FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
|
||||
}
|
||||
|
@@ -46,7 +46,9 @@ namespace keepass2android
|
||||
#endif
|
||||
|
||||
{
|
||||
private readonly int[] _buttonLengthButtonIds = new[] {Resource.Id.btn_length6,
|
||||
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,
|
||||
Resource.Id.btn_length16,
|
||||
@@ -259,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);
|
||||
|
||||
@@ -543,7 +545,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Toast.MakeText(this, e.Message, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(this, Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||
}
|
||||
|
||||
return password;
|
||||
|
@@ -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
|
||||
|
@@ -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";
|
||||
@@ -56,6 +58,8 @@ namespace keepass2android
|
||||
|
||||
public const int RequestCodeActivateRealSearch = 12366;
|
||||
|
||||
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
|
||||
|
||||
static readonly Dictionary<int /*resource id*/, int /*prio*/> bottomBarElementsPriority = new Dictionary<int, int>()
|
||||
{
|
||||
{ Resource.Id.cancel_insert_element, 20 },
|
||||
@@ -199,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();
|
||||
}
|
||||
|
||||
@@ -272,6 +275,7 @@ namespace keepass2android
|
||||
private IMenuItem searchItem;
|
||||
private IMenuItem searchItemDummy;
|
||||
private bool isPaused;
|
||||
private UpdateGroupBaseActivityBroadcastReceiver _dataUpdatedIntentReceiver;
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
@@ -743,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);
|
||||
|
||||
@@ -922,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))
|
||||
Toast.MakeText(activity, message, ToastLength.Long).Show();
|
||||
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||
}));
|
||||
var progressTask = new ProgressTask(App.Kp2a, this, moveElement);
|
||||
var progressTask = new BlockingOperationStarter(App.Kp2a, moveElement);
|
||||
progressTask.Run();
|
||||
|
||||
}
|
||||
@@ -1031,6 +1036,13 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
UnregisterReceiver(_dataUpdatedIntentReceiver);
|
||||
base.OnDestroy();
|
||||
|
||||
}
|
||||
|
||||
public override bool OnCreateOptionsMenu(IMenu menu)
|
||||
{
|
||||
|
||||
@@ -1210,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:
|
||||
@@ -1221,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);
|
||||
@@ -1291,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(() =>
|
||||
{
|
||||
Toast.MakeText(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message, ToastLength.Long).Show();
|
||||
});
|
||||
|
||||
App.Kp2a.Lock(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public bool IsBeingMoved(PwUuid uuid)
|
||||
{
|
||||
@@ -1406,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
|
||||
@@ -1468,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);
|
||||
@@ -1481,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;
|
||||
|
||||
@@ -1629,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;
|
||||
@@ -1660,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();
|
||||
}
|
||||
|
||||
|
@@ -111,9 +111,10 @@ namespace keepass2android
|
||||
SetResult (Result.Ok, intent);
|
||||
|
||||
Finish ();
|
||||
} else {
|
||||
Toast.MakeText (this, Resource.String.error_no_name, ToastLength.Long).Show ();
|
||||
}
|
||||
} else
|
||||
{
|
||||
App.Kp2a.ShowMessage(this, Resource.String.error_no_name, MessageSeverity.Error);
|
||||
}
|
||||
};
|
||||
|
||||
if (Intent.HasExtra(KeyGroupUuid))
|
||||
|
@@ -185,48 +185,56 @@ namespace keepass2android
|
||||
|
||||
public override View GetView(int position, View convertView, ViewGroup parent)
|
||||
{
|
||||
View currView;
|
||||
if(convertView == null)
|
||||
{
|
||||
LayoutInflater li = (LayoutInflater) _act.GetSystemService(LayoutInflaterService);
|
||||
currView = li.Inflate(Resource.Layout.icon, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
currView = convertView;
|
||||
}
|
||||
TextView tv = (TextView) currView.FindViewById(Resource.Id.icon_text);
|
||||
ImageView iv = (ImageView) currView.FindViewById(Resource.Id.icon_image);
|
||||
|
||||
if (position < (int)PwIcon.Count)
|
||||
{
|
||||
tv.Text = "" + position;
|
||||
var drawable = App.Kp2a.CurrentDb .DrawableFactory.GetIconDrawable(_act, App.Kp2a.CurrentDb.KpDatabase, (KeePassLib.PwIcon) position, null, false);
|
||||
drawable = new BitmapDrawable(Util.DrawableToBitmap(drawable));
|
||||
iv.SetImageDrawable(drawable);
|
||||
//App.Kp2a.GetDb().DrawableFactory.AssignDrawableTo(iv, _act, App.Kp2a.GetDb().KpDatabase, (KeePassLib.PwIcon) position, null, false);
|
||||
try
|
||||
{
|
||||
View currView;
|
||||
if (convertView == null)
|
||||
{
|
||||
LayoutInflater li = (LayoutInflater)_act.GetSystemService(LayoutInflaterService);
|
||||
currView = li.Inflate(Resource.Layout.icon, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
currView = convertView;
|
||||
}
|
||||
TextView tv = (TextView)currView.FindViewById(Resource.Id.icon_text);
|
||||
ImageView iv = (ImageView)currView.FindViewById(Resource.Id.icon_image);
|
||||
|
||||
if (
|
||||
PreferenceManager.GetDefaultSharedPreferences(currView.Context)
|
||||
.GetString("IconSetKey", currView.Context.PackageName) == currView.Context.PackageName)
|
||||
{
|
||||
Android.Graphics.PorterDuff.Mode mMode = Android.Graphics.PorterDuff.Mode.SrcAtop;
|
||||
Color color = new Color(189, 189, 189);
|
||||
iv.SetColorFilter(color, mMode);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int pos = position - (int)PwIcon.Count;
|
||||
var icon = _db.CustomIcons[pos];
|
||||
tv.Text = pos.ToString();
|
||||
iv.SetColorFilter(null);
|
||||
iv.SetImageBitmap(icon.Image);
|
||||
|
||||
}
|
||||
if (position < (int)PwIcon.Count)
|
||||
{
|
||||
tv.Text = "" + position;
|
||||
var drawable = App.Kp2a.CurrentDb.DrawableFactory.GetIconDrawable(_act, App.Kp2a.CurrentDb.KpDatabase, (KeePassLib.PwIcon)position, null, false);
|
||||
drawable = new BitmapDrawable(Util.DrawableToBitmap(drawable));
|
||||
iv.SetImageDrawable(drawable);
|
||||
//App.Kp2a.GetDb().DrawableFactory.AssignDrawableTo(iv, _act, App.Kp2a.GetDb().KpDatabase, (KeePassLib.PwIcon) position, null, false);
|
||||
|
||||
return currView;
|
||||
if (PreferenceManager.GetDefaultSharedPreferences(currView.Context)
|
||||
.GetString("IconSetKey", currView.Context.PackageName) == currView.Context.PackageName)
|
||||
{
|
||||
Android.Graphics.PorterDuff.Mode mMode = Android.Graphics.PorterDuff.Mode.SrcAtop;
|
||||
Color color = new Color(189, 189, 189);
|
||||
iv.SetColorFilter(color, mMode);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int pos = position - (int)PwIcon.Count;
|
||||
var icon = _db.CustomIcons[pos];
|
||||
tv.Text = pos.ToString();
|
||||
iv.SetColorFilter(null);
|
||||
iv.SetImageBitmap(icon.Image);
|
||||
|
||||
}
|
||||
|
||||
return currView;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool IsCustomIcon(int position)
|
||||
|