Compare commits

...

87 Commits

Author SHA1 Message Date
Philipp Crocoll
944f44bc4b Manifest for v1.09e-r4 2023-03-09 20:45:25 +01:00
Philipp Crocoll
58047d5386 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2023-03-09 20:44:29 +01:00
PhilippC
c0a06c9f3a Merge pull request #2264 from PhilippC/l10n_master2
New Crowdin updates
2023-03-09 20:44:02 +01:00
Philipp Crocoll
d0c041a0e2 remove debugging code 2023-03-09 20:16:24 +01:00
PhilippC
df060e2f4b New translations strings.xml (Portuguese, Brazilian) 2023-03-09 19:56:04 +01:00
PhilippC
aea55dad45 New translations strings.xml (Chinese Traditional) 2023-03-09 19:56:01 +01:00
PhilippC
5442dbf441 New translations strings.xml (Chinese Simplified) 2023-03-09 19:56:00 +01:00
PhilippC
317476d9b5 New translations strings.xml (Ukrainian) 2023-03-09 19:55:59 +01:00
PhilippC
ad0acb7a69 New translations strings.xml (Slovenian) 2023-03-09 19:55:56 +01:00
PhilippC
b66ae5d264 New translations strings.xml (Polish) 2023-03-09 19:55:53 +01:00
PhilippC
d87706fa43 New translations strings.xml (Japanese) 2023-03-09 19:55:51 +01:00
PhilippC
cb25d12709 New translations strings.xml (Greek) 2023-03-09 19:55:46 +01:00
PhilippC
dce536009e New translations strings.xml (German) 2023-03-09 19:55:45 +01:00
PhilippC
656e785214 New translations strings.xml (Danish) 2023-03-09 19:55:44 +01:00
PhilippC
35d50a6eb0 New translations strings.xml (French) 2023-03-09 19:55:39 +01:00
PhilippC
786bb646c2 New translations strings.xml (Slovak) 2023-03-09 19:55:37 +01:00
Philipp Crocoll
72cc6ff768 wrap adding fields and hints to dictionary, avoiding to add a duplicate key. Should close #2262 (but I can't reproduce) 2023-03-09 19:46:28 +01:00
Philipp Crocoll
404e07e5c0 Merge remote-tracking branch 'remotes/origin/l10n_master2' 2023-03-09 19:40:36 +01:00
Philipp Crocoll
1c7159ede9 changelog and manifest for 1.09e-r3 2023-03-09 19:39:51 +01:00
PhilippC
2378cd0d7c New translations strings.xml (Greek) 2023-03-08 16:54:54 +01:00
PhilippC
b149bab761 New translations strings.xml (Chinese Simplified) 2023-03-07 03:36:36 +01:00
PhilippC
5ebd8e5e33 New translations strings.xml (Portuguese, Brazilian) 2023-03-06 14:12:17 +01:00
Philipp Crocoll
db6b266a59 add more autofill tests and change AutofillParser to make them pass 2023-03-06 10:26:58 +01:00
Philipp Crocoll
7de28c5aba add preference to control if autofill view details are written to log 2023-03-06 10:26:33 +01:00
PhilippC
ed79df0c6d Merge pull request #2254 from PhilippC/PhilippC-autofill-testing-and-improvements
Autofill testing and improvements
2023-03-06 08:28:42 +01:00
Philipp Crocoll
4949fede32 remove tab in yml file 2023-03-06 08:05:57 +01:00
PhilippC
bddef6442c Merge pull request #2251 from WreckingBANG/master
Added Monochrome Icon
2023-03-06 07:59:11 +01:00
Philipp Crocoll
48a6d0a2ad disable macos build action 2023-03-06 07:58:52 +01:00
PhilippC
ac5f3c9ca5 Merge pull request #2244 from PhilippC/l10n_master2
New Crowdin updates
2023-03-04 09:08:30 +01:00
PhilippC
93e1cf1147 Merge pull request #2240 from hyproman/java-file-storage-android-studio
Get JavaFileStorage working in Android Studio
2023-03-04 09:01:19 +01:00
PhilippC
a805787a95 Merge pull request #2248 from hyproman/build-doc-update
Update build documentation
2023-03-04 09:00:56 +01:00
Philipp Crocoll
85315d0ecc run unit tests in github action 2023-02-28 22:42:04 +01:00
Philipp Crocoll
595a451f77 fix failing test 2023-02-28 22:37:32 +01:00
Philipp Crocoll
914224e4fa refactoring of autofill implementation, extracted some pieces to be independant of Android framework, added some xUnit tests 2023-02-28 22:31:28 +01:00
Philipp Crocoll
e350e8788c Merge branch 'master' into PhilippC-autofill-testing-and-improvements 2023-02-28 22:17:10 +01:00
PhilippC
ca5f6dc43c New translations strings.xml (Slovak) 2023-02-24 20:38:56 +01:00
WreckingBANG
0d4955622d Delete main.yml 2023-02-23 15:04:55 +01:00
WreckingBANG
886daa6b27 Update main.yml 2023-02-23 15:04:18 +01:00
WreckingBANG
8fa0803474 Create main.yml 2023-02-23 15:03:48 +01:00
WreckingBANG
4cad70e750 Update ic_launcher_offline_round.xml 2023-02-23 14:59:54 +01:00
WreckingBANG
c29b789a2b Update ic_launcher_offline.xml 2023-02-23 14:59:40 +01:00
WreckingBANG
cd34896661 Update ic_launcher_online_round.xml 2023-02-23 14:59:31 +01:00
WreckingBANG
1e02db86d6 Update ic_launcher_online.xml 2023-02-23 14:59:13 +01:00
Rick Brown
994741cbf5 Update build documentation based on my experience 2023-02-19 22:47:29 -05:00
PhilippC
58844be6eb New translations strings.xml (Danish) 2023-02-19 23:50:16 +01:00
PhilippC
2d899fa067 New translations strings.xml (Danish) 2023-02-19 22:32:16 +01:00
PhilippC
060bf6a6ee New translations strings.xml (Danish) 2023-02-19 21:31:30 +01:00
PhilippC
890f1bd704 New translations strings.xml (Danish) 2023-02-19 20:33:39 +01:00
PhilippC
139abcaec6 New translations strings.xml (German) 2023-02-18 23:26:56 +01:00
PhilippC
78a48b75b8 New translations strings.xml (German) 2023-02-18 22:13:41 +01:00
PhilippC
3918b06b1f New translations strings.xml (German) 2023-02-18 21:04:46 +01:00
PhilippC
40847ebe31 New translations strings.xml (German) 2023-02-18 19:55:30 +01:00
PhilippC
34cac86a9b New translations strings.xml (German) 2023-02-18 18:58:40 +01:00
PhilippC
d8598a53e0 New translations strings.xml (Japanese) 2023-02-18 17:41:17 +01:00
PhilippC
92d9eb1512 New translations strings.xml (German) 2023-02-18 11:15:28 +01:00
PhilippC
1be7b33f6b New translations strings.xml (German) 2023-02-18 10:09:38 +01:00
PhilippC
8464fa4f29 New translations strings.xml (German) 2023-02-18 09:11:16 +01:00
PhilippC
eff9a96bd5 New translations strings.xml (German) 2023-02-18 07:44:12 +01:00
PhilippC
bd4e321b0e New translations strings.xml (German) 2023-02-18 06:48:57 +01:00
PhilippC
47aaedbfb5 New translations strings.xml (German) 2023-02-18 06:48:56 +01:00
PhilippC
3043f8981d New translations strings.xml (German) 2023-02-18 06:48:55 +01:00
Philipp Crocoll
cfd413f1f4 add more hints for password fields to fix issues with autofill (maybe on newer Android versions only), closes #2184 2023-02-13 09:42:55 +01:00
Philipp Crocoll
30df03eec6 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2023-02-13 09:40:08 +01:00
PhilippC
5048f63204 Merge pull request #2225 from PhilippC/l10n_master2
New Crowdin updates
2023-02-13 09:37:38 +01:00
PhilippC
7557c0b9fd Merge pull request #2234 from hyproman/bugfix-2223-sftp-db-import-crash
Bugfix for #2223 - crash after import database by SFTP
2023-02-13 09:36:46 +01:00
PhilippC
9346f6bb32 Merge pull request #2226 from hyproman/gradle-version-sync
Reuse a single GradleDaemon instance
2023-02-13 09:36:35 +01:00
Rick Brown
23d7efff53 Bugfix for #2223 - crash after import database by SFTP
Add FLAG_MUTABLE flag to PendingIntent call for API >= 31 to fix an
issue where trying to open an SFTP database (transition to choose a
remote database file) crashes and returns to the Open/New database
screen.
2023-02-12 13:28:45 -05:00
PhilippC
c7eb2bf873 New translations strings.xml (Japanese) 2023-02-12 17:02:59 +01:00
Rick Brown
632121f3ec Get JavaFileStorage working in Android Studio
Resolve issue where AS would fail to import Android API jar
2023-02-11 18:34:20 -05:00
Philipp Crocoll
f7feddcf1f start to refactor Autofill code to extract some logic into an non-Androidlibrary 2023-02-11 06:56:02 +01:00
Philipp Crocoll
e745fee6e2 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2023-02-11 06:51:06 +01:00
Philipp Crocoll
bcc17d91bd fix potential crash on newer Android versions when trying to close notification drawer 2023-02-11 06:50:52 +01:00
Rick Brown
8c8a8e3968 Normalize gradle jvmargs to reuse a single GradleDaemon
Set max heap to 1.5G across all java projects. Inspection
via VisualVM found 1G to be a little too aggressive, while
2G seemed overkill. In any case a full build now can reuse
a single GradleDaemon instance instead of three.
2023-02-09 20:20:03 -05:00
PhilippC
3c41550404 New translations strings.xml (Polish) 2023-02-09 16:44:10 +01:00
PhilippC
76107b1207 Merge pull request #2207 from PhilippC/l10n_master2
New Crowdin updates
2023-02-06 20:48:03 +01:00
PhilippC
bb0c13b9d8 New translations strings.xml (Slovenian) 2023-02-06 18:57:26 +01:00
PhilippC
405166ba9d New translations strings.xml (Japanese) 2023-02-03 13:58:57 +01:00
PhilippC
f5cb60770e New translations strings.xml (Ukrainian) 2023-02-03 11:26:34 +01:00
PhilippC
fd287b8da7 New translations strings.xml (Basque) 2023-02-01 17:53:32 +01:00
PhilippC
9bea5b13e3 New translations strings.xml (Chinese Traditional) 2023-02-01 00:34:15 +01:00
PhilippC
aa6a728e8c New translations strings.xml (Chinese Traditional) 2023-01-31 23:35:12 +01:00
PhilippC
1c8431a3f9 New translations strings.xml (Chinese Traditional) 2023-01-31 22:35:51 +01:00
PhilippC
6a0eacd8f1 New translations strings.xml (Chinese Traditional) 2023-01-31 21:37:45 +01:00
PhilippC
d27976b737 New translations strings.xml (Chinese Traditional) 2023-01-31 19:41:50 +01:00
PhilippC
f312b50f0c New translations strings.xml (Chinese Traditional) 2023-01-31 18:42:58 +01:00
PhilippC
deb169fece New translations strings.xml (Chinese Traditional) 2023-01-31 18:42:57 +01:00
PhilippC
2df656211d New translations strings.xml (Japanese) 2023-01-31 14:17:53 +01:00
64 changed files with 6490 additions and 1593 deletions

View File

@@ -3,109 +3,111 @@ name: Build keepass2android app
on: [push, pull_request]
jobs:
macos:
# macos:
# Disabled. Does not work, maybe due to nuget version, see https://github.com/PhilippC/keepass2android/actions/runs/4297640426/jobs/7490853348
# should work again when the Project solution is converted to sdk style .csproj files.
runs-on: macos-12
# runs-on: macos-12
steps:
- uses: actions/checkout@v3
# steps:
# - uses: actions/checkout@v3
- name: Fetch submodules
run: git submodule init && git submodule update
# - name: Fetch submodules
# run: git submodule init && git submodule update
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
# - name: Setup Gradle
# uses: gradle/gradle-build-action@v2
- name: Cache NuGet packages
uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('src/**/*.csproj', 'src/**/packages.config') }}
restore-keys: |
${{ runner.os }}-nuget-
# - name: Cache NuGet packages
# uses: actions/cache@v3
# with:
# path: ~/.nuget/packages
# key: ${{ runner.os }}-nuget-${{ hashFiles('src/**/*.csproj', 'src/**/packages.config') }}
# restore-keys: |
# ${{ runner.os }}-nuget-
# As per https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md#visual-studio-for-mac
- name: Switch to Visual Studio 2019
if: ${{ false }} # Not needed. We stay with the default 'Visual Studio 2022' of macos-12 runner.
run: |
mv "/Applications/Visual Studio.app" "/Applications/Visual Studio 2022.app"
mv "/Applications/Visual Studio 2019.app" "/Applications/Visual Studio.app"
# # As per https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md#visual-studio-for-mac
# - name: Switch to Visual Studio 2019
# if: ${{ false }} # Not needed. We stay with the default 'Visual Studio 2022' of macos-12 runner.
# run: |
# mv "/Applications/Visual Studio.app" "/Applications/Visual Studio 2022.app"
# mv "/Applications/Visual Studio 2019.app" "/Applications/Visual Studio.app"
# As of 2022-12-02, keepass2android doesn't build with Xamarin >= 12.1 because there is some issue with SamsungPass. Removing SamsungPass would make the build succeed.
- name: Set default Xamarin SDK versions
run: |
# If using the github runner 'macos-12'
#$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.3
#$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.0
#$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.1 # Build fails in this case, as of 2022-12-02 : Xamarin/Android/Xamarin.Android.D8.targets(79,5): error : java.lang.ArrayIndexOutOfBoundsException : Index 4 out of bounds for length 4
#$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.2 # Build fails in this case, as of 2022-12-02 : Xamarin/Android/Xamarin.Android.D8.targets(79,5): error : java.lang.ArrayIndexOutOfBoundsException : Index 4 out of bounds for length 4
#$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.3 # Build fails in this case, as of 2022-12-02
$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=13.1
# # As of 2022-12-02, keepass2android doesn't build with Xamarin >= 12.1 because there is some issue with SamsungPass. Removing SamsungPass would make the build succeed.
# - name: Set default Xamarin SDK versions
# run: |
# # If using the github runner 'macos-12'
# #$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.3
# #$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.0
# #$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.1 # Build fails in this case, as of 2022-12-02 : Xamarin/Android/Xamarin.Android.D8.targets(79,5): error : java.lang.ArrayIndexOutOfBoundsException : Index 4 out of bounds for length 4
# #$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.2 # Build fails in this case, as of 2022-12-02 : Xamarin/Android/Xamarin.Android.D8.targets(79,5): error : java.lang.ArrayIndexOutOfBoundsException : Index 4 out of bounds for length 4
# #$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.3 # Build fails in this case, as of 2022-12-02
# $VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=13.1
# If using the github runner 'macos-11'
#$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.0
#$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.0
# # If using the github runner 'macos-11'
# #$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.0
# #$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=12.0
# If using the github runner 'macos-10.15'
# $VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2
# # If using the github runner 'macos-10.15'
# # $VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2
- name: Switch to JDK-11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
# - name: Switch to JDK-11
# uses: actions/setup-java@v3
# with:
# java-version: '11'
# distribution: 'temurin'
- name: Display java version
run: java -version
# - name: Display java version
# run: java -version
# Some components of Keepass2Android currently target android API 26 which are not available on the runner
- name: Download android-26 API
run: $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-26"
# # Some components of Keepass2Android currently target android API 26 which are not available on the runner
# - name: Download android-26 API
# run: $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-26"
- name: Build native dependencies
run: make native
# - name: Build native dependencies
# run: make native
- name: Build java dependencies
run: make java
# - name: Build java dependencies
# run: make java
- name: Install NuGet dependencies (net)
run: make nuget Flavor=Net
# - name: Install NuGet dependencies (net)
# run: make nuget Flavor=Net
- name: Build keepass2android (net)
run: |
make msbuild Flavor=Net
# - name: Build keepass2android (net)
# run: |
# make msbuild Flavor=Net
- name: Build APK (net)
run: |
make apk Flavor=Net
# - name: Build APK (net)
# run: |
# make apk Flavor=Net
- name: Archive production artifacts (net)
uses: actions/upload-artifact@v3
with:
name: signed APK ('net' built on ${{ github.job }})
path: |
src/keepass2android/bin/*/*-Signed.apk
# - name: Archive production artifacts (net)
# uses: actions/upload-artifact@v3
# with:
# name: signed APK ('net' built on ${{ github.job }})
# path: |
# src/keepass2android/bin/*/*-Signed.apk
- name: Install NuGet dependencies (nonet)
run: make nuget Flavor=NoNet
# - name: Install NuGet dependencies (nonet)
# run: make nuget Flavor=NoNet
- name: Build keepass2android (nonet)
run: |
make msbuild Flavor=NoNet
# - name: Build keepass2android (nonet)
# run: |
# make msbuild Flavor=NoNet
- name: Build APK (nonet)
run: |
make apk Flavor=NoNet
# - name: Build APK (nonet)
# run: |
# make apk Flavor=NoNet
- name: Archive production artifacts (nonet)
uses: actions/upload-artifact@v3
with:
name: signed APK ('nonet' built on ${{ github.job }})
path: |
src/keepass2android/bin/*/*-Signed.apk
# - name: Archive production artifacts (nonet)
# uses: actions/upload-artifact@v3
# with:
# name: signed APK ('nonet' built on ${{ github.job }})
# path: |
# src/keepass2android/bin/*/*-Signed.apk
- name: Perform "make distclean"
run: make distclean
# - name: Perform "make distclean"
# run: make distclean
# linux:
# disabled.
@@ -330,6 +332,9 @@ jobs:
- name: Build keepass2android (nonet)
run: |
make msbuild Flavor=NoNet
- name: Test Autofill
working-directory: ./src/Kp2aAutofillParserTest
run: dotnet test
- name: Build APK (nonet)
run: |

View File

@@ -43,7 +43,7 @@ By using the command line, you can build on Windows, macOS or Linux.
- 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)
- ~~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
@@ -64,9 +64,11 @@ 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\`)
- `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.
@@ -103,6 +105,10 @@ This is done on the command line and requires the Android SDK & NDK and Java JDK
- 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

View File

@@ -25,6 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCloudBindings", "PCloudBin
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "keepass2android-app", "keepass2android\keepass2android-app.csproj", "{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kp2aAutofillParser", "Kp2aAutofillParser\Kp2aAutofillParser.csproj", "{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kp2aAutofillParserTest", "Kp2aAutofillParserTest\Kp2aAutofillParserTest.csproj", "{3D1560FF-86BB-4CB4-8367-80BA13B81C38}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -283,6 +287,54 @@ Global
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|x64.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|x64.Deploy.0 = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Debug|Win32.ActiveCfg = Debug|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Debug|Win32.Build.0 = Debug|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Debug|x64.ActiveCfg = Debug|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Debug|x64.Build.0 = Debug|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Release|Any CPU.Build.0 = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Release|Win32.ActiveCfg = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Release|Win32.Build.0 = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Release|x64.ActiveCfg = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.Release|x64.Build.0 = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{39B12571-BAFE-4D3A-AEE2-4D74F14DFD96}.ReleaseNoNet|x64.Build.0 = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Debug|Win32.ActiveCfg = Debug|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Debug|Win32.Build.0 = Debug|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Debug|x64.ActiveCfg = Debug|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Debug|x64.Build.0 = Debug|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Release|Any CPU.Build.0 = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Release|Win32.ActiveCfg = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Release|Win32.Build.0 = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Release|x64.ActiveCfg = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.Release|x64.Build.0 = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -0,0 +1,984 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Newtonsoft.Json;
using Formatting = System.Xml.Formatting;
namespace Kp2aAutofillParser
{
public class W3cHints
{
// Supported W3C autofill tokens (https://html.spec.whatwg.org/multipage/forms.html#autofill)
public const string HONORIFIC_PREFIX = "honorific-prefix";
public const string NAME = "name";
public const string GIVEN_NAME = "given-name";
public const string ADDITIONAL_NAME = "additional-name";
public const string FAMILY_NAME = "family-name";
public const string HONORIFIC_SUFFIX = "honorific-suffix";
public const string USERNAME = "username";
public const string NEW_PASSWORD = "new-password";
public const string CURRENT_PASSWORD = "current-password";
public const string ORGANIZATION_TITLE = "organization-title";
public const string ORGANIZATION = "organization";
public const string STREET_ADDRESS = "street-address";
public const string ADDRESS_LINE1 = "address-line1";
public const string ADDRESS_LINE2 = "address-line2";
public const string ADDRESS_LINE3 = "address-line3";
public const string ADDRESS_LEVEL4 = "address-level4";
public const string ADDRESS_LEVEL3 = "address-level3";
public const string ADDRESS_LEVEL2 = "address-level2";
public const string ADDRESS_LEVEL1 = "address-level1";
public const string COUNTRY = "country";
public const string COUNTRY_NAME = "country-name";
public const string POSTAL_CODE = "postal-code";
public const string CC_NAME = "cc-name";
public const string CC_GIVEN_NAME = "cc-given-name";
public const string CC_ADDITIONAL_NAME = "cc-additional-name";
public const string CC_FAMILY_NAME = "cc-family-name";
public const string CC_NUMBER = "cc-number";
public const string CC_EXPIRATION = "cc-exp";
public const string CC_EXPIRATION_MONTH = "cc-exp-month";
public const string CC_EXPIRATION_YEAR = "cc-exp-year";
public const string CC_CSC = "cc-csc";
public const string CC_TYPE = "cc-type";
public const string TRANSACTION_CURRENCY = "transaction-currency";
public const string TRANSACTION_AMOUNT = "transaction-amount";
public const string LANGUAGE = "language";
public const string BDAY = "bday";
public const string BDAY_DAY = "bday-day";
public const string BDAY_MONTH = "bday-month";
public const string BDAY_YEAR = "bday-year";
public const string SEX = "sex";
public const string URL = "url";
public const string PHOTO = "photo";
// Optional W3C prefixes
public const string PREFIX_SECTION = "section-";
public const string SHIPPING = "shipping";
public const string BILLING = "billing";
// W3C prefixes below...
public const string PREFIX_HOME = "home";
public const string PREFIX_WORK = "work";
public const string PREFIX_FAX = "fax";
public const string PREFIX_PAGER = "pager";
// ... require those suffix
public const string TEL = "tel";
public const string TEL_COUNTRY_CODE = "tel-country-code";
public const string TEL_NATIONAL = "tel-national";
public const string TEL_AREA_CODE = "tel-area-code";
public const string TEL_LOCAL = "tel-local";
public const string TEL_LOCAL_PREFIX = "tel-local-prefix";
public const string TEL_LOCAL_SUFFIX = "tel-local-suffix";
public const string TEL_EXTENSION = "tel_extension";
public const string EMAIL = "email";
public const string IMPP = "impp";
private W3cHints()
{
}
public static bool isW3cSectionPrefix(string hint)
{
return hint.ToLower().StartsWith(W3cHints.PREFIX_SECTION);
}
public static bool isW3cAddressType(string hint)
{
switch (hint.ToLower())
{
case W3cHints.SHIPPING:
case W3cHints.BILLING:
return true;
}
return false;
}
public static bool isW3cTypePrefix(string hint)
{
switch (hint.ToLower())
{
case W3cHints.PREFIX_WORK:
case W3cHints.PREFIX_FAX:
case W3cHints.PREFIX_HOME:
case W3cHints.PREFIX_PAGER:
return true;
}
return false;
}
public static bool isW3cTypeHint(string hint)
{
switch (hint.ToLower())
{
case W3cHints.TEL:
case W3cHints.TEL_COUNTRY_CODE:
case W3cHints.TEL_NATIONAL:
case W3cHints.TEL_AREA_CODE:
case W3cHints.TEL_LOCAL:
case W3cHints.TEL_LOCAL_PREFIX:
case W3cHints.TEL_LOCAL_SUFFIX:
case W3cHints.TEL_EXTENSION:
case W3cHints.EMAIL:
case W3cHints.IMPP:
return true;
}
return false;
}
}
/// <summary>
/// FilledAutofillFieldCollection is the model that holds all of the data on a client app's page,
/// plus the dataset name associated with it.
/// </summary>
public class FilledAutofillFieldCollection<FieldT> where FieldT:InputField
{
public Dictionary<string, FilledAutofillField<FieldT>> HintMap { get; }
public string DatasetName { get; set; }
public FilledAutofillFieldCollection(Dictionary<string, FilledAutofillField<FieldT>> hintMap, string datasetName = "")
{
//recreate hint map making sure we compare case insensitive
HintMap = BuildHintMap();
foreach (var p in hintMap)
HintMap.Add(p.Key, p.Value);
DatasetName = datasetName;
}
public FilledAutofillFieldCollection() : this(BuildHintMap())
{ }
private static Dictionary<string, FilledAutofillField<FieldT>> BuildHintMap()
{
return new Dictionary<string, FilledAutofillField<FieldT>>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Adds a filledAutofillField to the collection, indexed by all of its hints.
/// </summary>
/// <returns>The add.</returns>
/// <param name="filledAutofillField">Filled autofill field.</param>
public void Add(FilledAutofillField<FieldT> filledAutofillField)
{
foreach (string hint in filledAutofillField.AutofillHints)
{
if (AutofillHintsHelper.IsSupportedHint(hint))
{
HintMap.TryAdd(hint, filledAutofillField);
}
}
}
/// <summary>
/// Takes in a list of autofill hints (`autofillHints`), usually associated with a View or set of
/// Views. Returns whether any of the filled fields on the page have at least 1 of these
/// `autofillHint`s.
/// </summary>
/// <returns><c>true</c>, if with hints was helpsed, <c>false</c> otherwise.</returns>
/// <param name="autofillHints">Autofill hints.</param>
public bool HelpsWithHints(List<string> autofillHints)
{
for (int i = 0; i < autofillHints.Count; i++)
{
var autofillHint = autofillHints[i];
if (HintMap.ContainsKey(autofillHint) && !HintMap[autofillHint].IsNull())
{
return true;
}
}
return false;
}
}
public class AutofillHintsHelper
{
public const string AutofillHint2faAppOtp = "2faAppOTPCode";
public const string AutofillHintBirthDateDay = "birthDateDay";
public const string AutofillHintBirthDateFull = "birthDateFull";
public const string AutofillHintBirthDateMonth = "birthDateMonth";
public const string AutofillHintBirthDateYear = "birthDateYear";
public const string AutofillHintCreditCardExpirationDate = "creditCardExpirationDate";
public const string AutofillHintCreditCardExpirationDay = "creditCardExpirationDay";
public const string AutofillHintCreditCardExpirationMonth = "creditCardExpirationMonth";
public const string AutofillHintCreditCardExpirationYear = "creditCardExpirationYear";
public const string AutofillHintCreditCardNumber = "creditCardNumber";
public const string AutofillHintCreditCardSecurityCode = "creditCardSecurityCode";
public const string AutofillHintEmailAddress = "emailAddress";
public const string AutofillHintEmailOtp = "emailOTPCode";
public const string AutofillHintGender = "gender";
public const string AutofillHintName = "name";
public const string AutofillHintNewPassword = "newPassword";
public const string AutofillHintNewUsername = "newUsername";
public const string AutofillHintNotApplicable = "notApplicable";
public const string AutofillHintPassword = "password";
public const string AutofillHintPersonName = "personName";
public const string AutofillHintPersonNameFAMILY = "personFamilyName";
public const string AutofillHintPersonNameGIVEN = "personGivenName";
public const string AutofillHintPersonNameMIDDLE = "personMiddleName";
public const string AutofillHintPersonNameMIDDLE_INITIAL = "personMiddleInitial";
public const string AutofillHintPersonNamePREFIX = "personNamePrefix";
public const string AutofillHintPersonNameSUFFIX = "personNameSuffix";
public const string AutofillHintPhone = "phone";
public const string AutofillHintPhoneContryCode = "phoneCountryCode";
public const string AutofillHintPostalAddressAPT_NUMBER = "aptNumber";
public const string AutofillHintPostalAddressCOUNTRY = "addressCountry";
public const string AutofillHintPostalAddressDEPENDENT_LOCALITY = "dependentLocality";
public const string AutofillHintPostalAddressEXTENDED_ADDRESS = "extendedAddress";
public const string AutofillHintPostalAddressEXTENDED_POSTAL_CODE = "extendedPostalCode";
public const string AutofillHintPostalAddressLOCALITY = "addressLocality";
public const string AutofillHintPostalAddressREGION = "addressRegion";
public const string AutofillHintPostalAddressSTREET_ADDRESS = "streetAddress";
public const string AutofillHintPostalCode = "postalCode";
public const string AutofillHintPromoCode = "promoCode";
public const string AutofillHintSMS_OTP = "smsOTPCode";
public const string AutofillHintUPI_VPA = "upiVirtualPaymentAddress";
public const string AutofillHintUsername = "username";
public const string AutofillHintWifiPassword = "wifiPassword";
public const string AutofillHintPhoneNational = "phoneNational";
public const string AutofillHintPhoneNumber = "phoneNumber";
public const string AutofillHintPhoneNumberDevice = "phoneNumberDevice";
public const string AutofillHintPostalAddress = "postalAddress";
private static readonly HashSet<string> _allSupportedHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
AutofillHintCreditCardExpirationDate,
AutofillHintCreditCardExpirationDay,
AutofillHintCreditCardExpirationMonth,
AutofillHintCreditCardExpirationYear,
AutofillHintCreditCardNumber,
AutofillHintCreditCardSecurityCode,
AutofillHintEmailAddress,
AutofillHintPhone,
AutofillHintName,
AutofillHintPassword,
AutofillHintPostalAddress,
AutofillHintPostalCode,
AutofillHintUsername,
W3cHints.HONORIFIC_PREFIX,
W3cHints.NAME,
W3cHints.GIVEN_NAME,
W3cHints.ADDITIONAL_NAME,
W3cHints.FAMILY_NAME,
W3cHints.HONORIFIC_SUFFIX,
W3cHints.USERNAME,
W3cHints.NEW_PASSWORD,
W3cHints.CURRENT_PASSWORD,
W3cHints.ORGANIZATION_TITLE,
W3cHints.ORGANIZATION,
W3cHints.STREET_ADDRESS,
W3cHints.ADDRESS_LINE1,
W3cHints.ADDRESS_LINE2,
W3cHints.ADDRESS_LINE3,
W3cHints.ADDRESS_LEVEL4,
W3cHints.ADDRESS_LEVEL3,
W3cHints.ADDRESS_LEVEL2,
W3cHints.ADDRESS_LEVEL1,
W3cHints.COUNTRY,
W3cHints.COUNTRY_NAME,
W3cHints.POSTAL_CODE,
W3cHints.CC_NAME,
W3cHints.CC_GIVEN_NAME,
W3cHints.CC_ADDITIONAL_NAME,
W3cHints.CC_FAMILY_NAME,
W3cHints.CC_NUMBER,
W3cHints.CC_EXPIRATION,
W3cHints.CC_EXPIRATION_MONTH,
W3cHints.CC_EXPIRATION_YEAR,
W3cHints.CC_CSC,
W3cHints.CC_TYPE,
W3cHints.TRANSACTION_CURRENCY,
W3cHints.TRANSACTION_AMOUNT,
W3cHints.LANGUAGE,
W3cHints.BDAY,
W3cHints.BDAY_DAY,
W3cHints.BDAY_MONTH,
W3cHints.BDAY_YEAR,
W3cHints.SEX,
W3cHints.URL,
W3cHints.PHOTO,
W3cHints.TEL,
W3cHints.TEL_COUNTRY_CODE,
W3cHints.TEL_NATIONAL,
W3cHints.TEL_AREA_CODE,
W3cHints.TEL_LOCAL,
W3cHints.TEL_LOCAL_PREFIX,
W3cHints.TEL_LOCAL_SUFFIX,
W3cHints.TEL_EXTENSION,
W3cHints.EMAIL,
W3cHints.IMPP,
};
private static readonly List<HashSet<string>> partitionsOfCanonicalHints = new List<HashSet<string>>()
{
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
AutofillHintEmailAddress,
AutofillHintPhone,
AutofillHintName,
AutofillHintPassword,
AutofillHintUsername,
W3cHints.HONORIFIC_PREFIX,
W3cHints.EMAIL,
W3cHints.NAME,
W3cHints.GIVEN_NAME,
W3cHints.ADDITIONAL_NAME,
W3cHints.FAMILY_NAME,
W3cHints.HONORIFIC_SUFFIX,
W3cHints.ORGANIZATION_TITLE,
W3cHints.ORGANIZATION,
W3cHints.LANGUAGE,
W3cHints.BDAY,
W3cHints.BDAY_DAY,
W3cHints.BDAY_MONTH,
W3cHints.BDAY_YEAR,
W3cHints.SEX,
W3cHints.URL,
W3cHints.PHOTO,
W3cHints.TEL,
W3cHints.TEL_COUNTRY_CODE,
W3cHints.TEL_NATIONAL,
W3cHints.TEL_AREA_CODE,
W3cHints.TEL_LOCAL,
W3cHints.TEL_LOCAL_PREFIX,
W3cHints.TEL_LOCAL_SUFFIX,
W3cHints.TEL_EXTENSION,
W3cHints.IMPP,
},
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
AutofillHintPostalAddress,
AutofillHintPostalCode,
W3cHints.STREET_ADDRESS,
W3cHints.ADDRESS_LINE1,
W3cHints.ADDRESS_LINE2,
W3cHints.ADDRESS_LINE3,
W3cHints.ADDRESS_LEVEL4,
W3cHints.ADDRESS_LEVEL3,
W3cHints.ADDRESS_LEVEL2,
W3cHints.ADDRESS_LEVEL1,
W3cHints.COUNTRY,
W3cHints.COUNTRY_NAME
},
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
AutofillHintCreditCardExpirationDate,
AutofillHintCreditCardExpirationDay,
AutofillHintCreditCardExpirationMonth,
AutofillHintCreditCardExpirationYear,
AutofillHintCreditCardNumber,
AutofillHintCreditCardSecurityCode,
W3cHints.CC_NAME,
W3cHints.CC_GIVEN_NAME,
W3cHints.CC_ADDITIONAL_NAME,
W3cHints.CC_FAMILY_NAME,
W3cHints.CC_TYPE,
W3cHints.TRANSACTION_CURRENCY,
W3cHints.TRANSACTION_AMOUNT,
},
};
private static readonly Dictionary<string, string> hintToCanonicalReplacement = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{W3cHints.EMAIL, AutofillHintEmailAddress},
{W3cHints.USERNAME, AutofillHintUsername},
{W3cHints.CURRENT_PASSWORD, AutofillHintPassword},
{W3cHints.NEW_PASSWORD, AutofillHintPassword},
{W3cHints.CC_EXPIRATION_MONTH, AutofillHintCreditCardExpirationMonth },
{W3cHints.CC_EXPIRATION_YEAR, AutofillHintCreditCardExpirationYear },
{W3cHints.CC_EXPIRATION, AutofillHintCreditCardExpirationDate },
{W3cHints.CC_NUMBER, AutofillHintCreditCardNumber },
{W3cHints.CC_CSC, AutofillHintCreditCardSecurityCode },
{W3cHints.POSTAL_CODE, AutofillHintPostalCode },
};
public static bool IsSupportedHint(string hint)
{
return _allSupportedHints.Contains(hint);
}
public static string[] FilterForSupportedHints(string[] hints)
{
if (hints == null)
return Array.Empty<string>();
var filteredHints = new string[hints.Length];
int i = 0;
foreach (var hint in hints)
{
if (IsSupportedHint(hint))
{
filteredHints[i++] = hint;
}
}
var finalFilteredHints = new string[i];
Array.Copy(filteredHints, 0, finalFilteredHints, 0, i);
return finalFilteredHints;
}
/// <summary>
/// transforms hints by replacing some W3cHints by their Android counterparts and transforming everything to lowercase
/// </summary>
public static List<string> ConvertToCanonicalLowerCaseHints(string[] supportedHints)
{
List<string> result = new List<string>();
foreach (string hint in supportedHints)
{
var canonicalHint = ToCanonicalHint(hint);
result.Add(canonicalHint.ToLower());
}
return result;
}
public static string ToCanonicalHint(string hint)
{
string canonicalHint;
if (!hintToCanonicalReplacement.TryGetValue(hint, out canonicalHint))
canonicalHint = hint;
return canonicalHint;
}
public static int GetPartitionIndex(string hint)
{
for (int i = 0; i < partitionsOfCanonicalHints.Count; i++)
{
if (partitionsOfCanonicalHints[i].Contains(hint))
{
return i;
}
}
return -1;
}
public static FilledAutofillFieldCollection<FieldT> FilterForPartition<FieldT>(FilledAutofillFieldCollection<FieldT> autofillFields, int partitionIndex) where FieldT: InputField
{
FilledAutofillFieldCollection<FieldT> filteredCollection =
new FilledAutofillFieldCollection<FieldT> { DatasetName = autofillFields.DatasetName };
if (partitionIndex == -1)
return filteredCollection;
foreach (var field in autofillFields.HintMap.Values.Distinct())
{
foreach (var hint in field.AutofillHints)
{
if (GetPartitionIndex(hint) == partitionIndex)
{
filteredCollection.Add(field);
break;
}
}
}
return filteredCollection;
}
public static FilledAutofillFieldCollection<FieldT> FilterForPartition<FieldT>(FilledAutofillFieldCollection<FieldT> filledAutofillFieldCollection, List<string> autofillFieldsFocusedAutofillCanonicalHints) where FieldT: InputField
{
//only apply partition data if we have FocusedAutofillCanonicalHints. This may be empty on buggy Firefox.
if (autofillFieldsFocusedAutofillCanonicalHints.Any())
{
int partitionIndex = AutofillHintsHelper.GetPartitionIndex(autofillFieldsFocusedAutofillCanonicalHints.FirstOrDefault());
return AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, partitionIndex);
}
return filledAutofillFieldCollection;
}
}
/// <summary>
/// This enum represents the Android.Text.InputTypes values. For testability, this is duplicated here.
/// </summary>
public enum InputTypes
{
ClassDatetime = 4,
ClassNumber = 2,
ClassPhone = 3,
ClassText = 1,
DatetimeVariationDate = 16,
DatetimeVariationNormal = 0,
DatetimeVariationTime = 32,
MaskClass = 15,
MaskFlags = 16773120,
MaskVariation = 4080,
Null = 0,
NumberFlagDecimal = 8192,
NumberFlagSigned = 4096,
NumberVariationNormal = 0,
NumberVariationPassword = 16,
TextFlagAutoComplete = 65536,
TextFlagAutoCorrect = 32768,
TextFlagCapCharacters = 4096,
TextFlagCapSentences = 16384,
TextFlagCapWords = 8192,
TextFlagEnableTextConversionSuggestions = 1048576,
TextFlagImeMultiLine = 262144,
TextFlagMultiLine = 131072,
TextFlagNoSuggestions = 524288,
TextVariationEmailAddress = 32,
TextVariationEmailSubject = 48,
TextVariationFilter = 176,
TextVariationLongMessage = 80,
TextVariationNormal = 0,
TextVariationPassword = 128,
TextVariationPersonName = 96,
TextVariationPhonetic = 192,
TextVariationPostalAddress = 112,
TextVariationShortMessage = 64,
TextVariationUri = 16,
TextVariationVisiblePassword = 144,
TextVariationWebEditText = 160,
TextVariationWebEmailAddress = 208,
TextVariationWebPassword = 224
}
public interface IKp2aDigitalAssetLinksDataSource
{
bool IsTrustedApp(string packageName);
bool IsTrustedLink(string domain, string targetPackage);
bool IsEnabled();
}
class TimeUtil
{
private static DateTime? m_dtUnixRoot = null;
public static DateTime ConvertUnixTime(double dtUnix)
{
try
{
if (!m_dtUnixRoot.HasValue)
m_dtUnixRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0,
DateTimeKind.Utc)).ToLocalTime();
return m_dtUnixRoot.Value.AddSeconds(dtUnix);
}
catch (Exception) { Debug.Assert(false); }
return DateTime.UtcNow;
}
}
public class FilledAutofillField<FieldT> where FieldT : InputField
{
private string[] _autofillHints;
public string TextValue { get; set; }
public long? DateValue { get; set; }
public bool? ToggleValue { get; set; }
public string ValueToString()
{
if (DateValue != null)
{
return TimeUtil.ConvertUnixTime((long)DateValue / 1000.0).ToLongDateString();
}
if (ToggleValue != null)
return ToggleValue.ToString();
return TextValue;
}
/// <summary>
/// returns the autofill hints for the filled field. These are always lowercased for simpler string comparison.
/// </summary>
public string[] AutofillHints
{
get
{
return _autofillHints;
}
set
{
_autofillHints = value;
for (int i = 0; i < _autofillHints.Length; i++)
_autofillHints[i] = _autofillHints[i].ToLower();
}
}
public FilledAutofillField()
{ }
public FilledAutofillField(FieldT inputField)
: this(inputField, inputField.AutofillHints)
{
}
public FilledAutofillField(FieldT inputField, string[] hints)
{
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(hints);
List<string> hintList = new List<string>();
string nextHint = null;
for (int i = 0; i < rawHints.Length; i++)
{
string hint = rawHints[i];
if (i < rawHints.Length - 1)
{
nextHint = rawHints[i + 1];
}
// First convert the compound W3C autofill hints
if (W3cHints.isW3cSectionPrefix(hint) && i < rawHints.Length - 1)
{
hint = rawHints[++i];
if (i < rawHints.Length - 1)
{
nextHint = rawHints[i + 1];
}
}
if (W3cHints.isW3cTypePrefix(hint) && nextHint != null && W3cHints.isW3cTypeHint(nextHint))
{
hint = nextHint;
i++;
}
if (W3cHints.isW3cAddressType(hint) && nextHint != null)
{
hint = nextHint;
i++;
}
// Then check if the "actual" hint is supported.
if (AutofillHintsHelper.IsSupportedHint(hint))
{
hintList.Add(hint);
}
else
{
}
}
AutofillHints = AutofillHintsHelper.ConvertToCanonicalLowerCaseHints(hintList.ToArray()).ToArray();
}
public bool IsNull()
{
return TextValue == null && DateValue == null && ToggleValue == null;
}
public override bool Equals(object obj)
{
if (this == obj) return true;
if (obj == null || GetType() != obj.GetType()) return false;
FilledAutofillField<FieldT> that = (FilledAutofillField<FieldT>)obj;
if (!TextValue?.Equals(that.TextValue) ?? that.TextValue != null)
return false;
if (DateValue != null ? !DateValue.Equals(that.DateValue) : that.DateValue != null)
return false;
return ToggleValue != null ? ToggleValue.Equals(that.ToggleValue) : that.ToggleValue == null;
}
public override int GetHashCode()
{
unchecked
{
var result = TextValue != null ? TextValue.GetHashCode() : 0;
result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
return result;
}
}
}
/// <summary>
/// Base class for everything that is (or could be) an input field which might (or might not) be autofilled.
/// For testability, this is independent from Android classes like ViewNode
/// </summary>
public abstract class InputField
{
public string? IdEntry { get; set; }
public string? Hint { get; set; }
public string ClassName { get; set; }
public string[] AutofillHints { get; set; }
public bool IsFocused { get; set; }
public InputTypes InputType { get; set; }
public string HtmlInfoTag { get; set; }
public string HtmlInfoTypeAttribute { get; set; }
}
/// <summary>
/// Serializable structure defining the contents of the current view (from an autofill perspective)
/// </summary>
/// <typeparam name="TField"></typeparam>
public class AutofillView<TField> where TField : InputField
{
public List<TField> InputFields { get; set; } = new List<TField>();
public string PackageId { get; set; } = null;
public string WebDomain { get; set; } = null;
}
public interface ILogger
{
void Log(string x);
}
public class StructureParserBase<FieldT> where FieldT: InputField
{
private readonly ILogger _log;
private readonly IKp2aDigitalAssetLinksDataSource _digitalAssetLinksDataSource;
private readonly List<string> _autofillHintsForLogin = new List<string>
{
AutofillHintsHelper.AutofillHintPassword,
AutofillHintsHelper.AutofillHintUsername,
AutofillHintsHelper.AutofillHintEmailAddress
};
public string PackageId { get; set; }
public Dictionary<FieldT, string[]> FieldsMappedToHints = new Dictionary<FieldT, string[]>();
public StructureParserBase(ILogger logger, IKp2aDigitalAssetLinksDataSource digitalAssetLinksDataSource)
{
_log = logger;
_digitalAssetLinksDataSource = digitalAssetLinksDataSource;
}
public class AutofillTargetId
{
public string PackageName { get; set; }
public string PackageNameWithPseudoSchema
{
get { return AndroidAppScheme + PackageName; }
}
public const string AndroidAppScheme = "androidapp://";
public string WebDomain { get; set; }
/// <summary>
/// If PackageName and WebDomain are not compatible (by DAL or because PackageName is a trusted browser in which case we treat all domains as "compatible"
/// we need to issue a warning. If we would fill credentials for the package, a malicious website could try to get credentials for the app.
/// If we would fill credentials for the domain, a malicious app could get credentials for the domain.
/// </summary>
public bool IncompatiblePackageAndDomain { get; set; }
public string DomainOrPackage
{
get
{
return WebDomain ?? PackageNameWithPseudoSchema;
}
}
}
public AutofillTargetId ParseForFill(bool isManual, AutofillView<FieldT> autofillView)
{
return Parse(true, isManual, autofillView);
}
public AutofillTargetId ParseForSave(AutofillView<FieldT> autofillView)
{
return Parse(false, true, autofillView);
}
/// <summary>
/// Traverse AssistStructure and add ViewNode metadata to a flat list.
/// </summary>
/// <returns>The parse.</returns>
/// <param name="forFill">If set to <c>true</c> for fill.</param>
/// <param name="isManualRequest"></param>
protected virtual AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<FieldT> autofillView)
{
AutofillTargetId result = new AutofillTargetId()
{
PackageName = autofillView.PackageId,
WebDomain = autofillView.WebDomain
};
_log.Log("parsing autofillStructure...");
if (LogAutofillView)
{
string debugInfo = JsonConvert.SerializeObject(autofillView, Newtonsoft.Json.Formatting.Indented);
_log.Log("This is the autofillStructure: \n\n " + debugInfo);
}
//go through each input field and determine username/password fields.
//Depending on the target this can require more or less heuristics.
// * if there is a valid & supported autofill hint, we assume that all fields which should be filled do have an appropriate Autofill hint
// * if there is no such autofill hint, we use IsPassword to
HashSet<string> autofillHintsOfAllFields = autofillView.InputFields.Where(f => f.AutofillHints != null)
.SelectMany(f => f.AutofillHints).Select(AutofillHintsHelper.ToCanonicalHint).ToHashSet();
bool hasLoginAutofillHints = autofillHintsOfAllFields.Intersect(_autofillHintsForLogin).Any();
if (hasLoginAutofillHints)
{
foreach (var viewNode in autofillView.InputFields)
{
string[] viewHints = viewNode.AutofillHints;
if (viewHints == null)
continue;
if (viewHints.Select(AutofillHintsHelper.ToCanonicalHint).Intersect(_autofillHintsForLogin).Any())
{
AddFieldToHintMap(viewNode, viewHints.Select(AutofillHintsHelper.ToCanonicalHint).ToHashSet().ToArray());
}
}
}
else
{
//determine password fields, first by type, then by hint:
List<FieldT> editTexts = autofillView.InputFields.Where(f => IsEditText(f)).ToList();
List<FieldT> passwordFields = autofillView.InputFields.Where(f => IsEditText(f) && IsPassword(f)).ToList();
if (!passwordFields.Any())
{
passwordFields = autofillView.InputFields.Where(f => IsEditText(f) && HasPasswordHint(f)).ToList();
}
//determine username fields. Try by hint, if that fails use the one before the password
List<FieldT> usernameFields = autofillView.InputFields.Where(f => IsEditText(f) && HasUsernameHint(f)).ToList();
if (!usernameFields.Any())
{
foreach (var passwordField in passwordFields)
{
var lastInputBeforePassword = autofillView.InputFields.Where(IsEditText)
.TakeWhile(f => f != passwordField && !passwordFields.Contains(f)).LastOrDefault();
if (lastInputBeforePassword != null)
usernameFields.Add(lastInputBeforePassword);
}
}
//for "heuristic determination" we demand that one of the filled fields is focused:
if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused))
{
foreach (var uf in usernameFields)
AddFieldToHintMap(uf, new string[] { AutofillHintsHelper.AutofillHintUsername });
foreach (var pf in passwordFields.Except(usernameFields))
AddFieldToHintMap(pf, new string[] { AutofillHintsHelper.AutofillHintPassword });
}
}
if (!string.IsNullOrEmpty(autofillView.WebDomain) && _digitalAssetLinksDataSource.IsEnabled())
{
result.IncompatiblePackageAndDomain = !_digitalAssetLinksDataSource.IsTrustedLink(autofillView.WebDomain, result.PackageName);
if (result.IncompatiblePackageAndDomain)
{
_log.Log($"DAL verification failed for {result.PackageName}/{result.WebDomain}");
}
}
else
{
result.IncompatiblePackageAndDomain = false;
}
return result;
}
private void AddFieldToHintMap(FieldT field, string[] hints)
{
if (FieldsMappedToHints.ContainsKey(field))
{
FieldsMappedToHints[field] = FieldsMappedToHints[field].Concat(hints).ToArray();
}
else
{
FieldsMappedToHints[field] = hints;
}
}
public bool LogAutofillView { get; set; }
private bool IsEditText(FieldT f)
{
return (f.ClassName == "android.widget.EditText"
|| f.ClassName == "android.widget.AutoCompleteTextView"
|| f.HtmlInfoTag == "input");
}
private static readonly HashSet<string> _passwordHints = new HashSet<string> { "password", "passwort", "passwordAuto", "pswd" };
private static bool HasPasswordHint(InputField f)
{
return IsAny(f.IdEntry, _passwordHints) ||
IsAny(f.Hint, _passwordHints);
}
private static readonly HashSet<string> _usernameHints = new HashSet<string> { "email", "e-mail", "username", "user id" };
private static bool HasUsernameHint(InputField f)
{
return IsAny(f.IdEntry?.ToLower(), _usernameHints) ||
IsAny(f.Hint?.ToLower(), _usernameHints);
}
private static bool IsAny(string? value, IEnumerable<string> terms)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
var lowerValue = value.ToLowerInvariant();
return terms.Any(t => lowerValue == t);
}
private static bool IsInputTypeClass(InputTypes inputType, InputTypes inputTypeClass)
{
if (!InputTypes.MaskClass.HasFlag(inputTypeClass))
throw new Exception("invalid inputTypeClass");
return (((int)inputType) & (int)InputTypes.MaskClass) == (int)(inputTypeClass);
}
private static bool IsInputTypeVariation(InputTypes inputType, InputTypes inputTypeVariation)
{
if (!InputTypes.MaskVariation.HasFlag(inputTypeVariation))
throw new Exception("invalid inputTypeVariation");
return (((int)inputType) & (int)InputTypes.MaskVariation) == (int)(inputTypeVariation);
}
private static bool IsPassword(InputField f)
{
InputTypes inputType = f.InputType;
return
(!f.IdEntry?.ToLowerInvariant().Contains("search") ?? true) &&
(!f.Hint?.ToLowerInvariant().Contains("search") ?? true) &&
(
(IsInputTypeClass(inputType, InputTypes.ClassText)
&&
(
IsInputTypeVariation(inputType, InputTypes.TextVariationPassword)
|| IsInputTypeVariation(inputType, InputTypes.TextVariationVisiblePassword)
|| IsInputTypeVariation(inputType, InputTypes.TextVariationWebPassword)
)
)
|| (f.AutofillHints != null && f.AutofillHints.First() == "passwordAuto")
|| (f.HtmlInfoTypeAttribute == "password")
);
}
}
}

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,135 @@
using Kp2aAutofillParser;
using Newtonsoft.Json;
using System.IO;
using System.Reflection;
using Xunit.Abstractions;
namespace Kp2aAutofillParserTest
{
public class AutofillTest
{
private readonly ITestOutputHelper _testOutputHelper;
public AutofillTest(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
}
class TestInputField: InputField
{
public string[] ExpectedAssignedHints { get; set; }
}
[Fact]
public void TestNotFocusedPasswordAutoIsNotFilled()
{
var resourceName = "Kp2aAutofillParserTest.com-servicenet-mobile-no-focus.json";
RunTestFromAutofillInput(resourceName, "com.servicenet.mobile");
}
[Fact]
public void TestFocusedPasswordAutoIsFilled()
{
var resourceName = "Kp2aAutofillParserTest.com-servicenet-mobile-focused.json";
RunTestFromAutofillInput(resourceName, "com.servicenet.mobile" );
}
[Fact]
public void TestMulitpleUnfocusedLoginsIsFilled()
{
var resourceName = "Kp2aAutofillParserTest.firefox-amazon-it.json";
RunTestFromAutofillInput(resourceName, "org.mozilla.firefox", "www.amazon.it");
}
[Fact]
public void CanDetectFieldsWithoutAutofillHints()
{
var resourceName = "Kp2aAutofillParserTest.chrome-android10-amazon-it.json";
RunTestFromAutofillInput(resourceName, "com.android.chrome", "www.amazon.it");
}
[Fact]
public void DetectsUsernameFieldDespitePasswordAutoHint()
{
var resourceName = "Kp2aAutofillParserTest.com-ifs-banking-fiid3364-android13.json";
RunTestFromAutofillInput(resourceName, "com.ifs.banking.fiid3364", null);
}
[Fact]
public void DetectsEmailAutofillHint()
{
var resourceName = "Kp2aAutofillParserTest.com-expressvpn-vpn-android13.json";
RunTestFromAutofillInput(resourceName, "com.expressvpn.vpn", null);
}
private void RunTestFromAutofillInput(string resourceName, string expectedPackageName = null, string expectedWebDomain = null)
{
var assembly = Assembly.GetExecutingAssembly();
string input;
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
input = reader.ReadToEnd();
}
AutofillView<TestInputField>? autofillView =
JsonConvert.DeserializeObject<AutofillView<TestInputField>>(input);
StructureParserBase<TestInputField> parser =
new StructureParserBase<TestInputField>(new TestLogger(), new TestDalSourceTrustAll());
var result = parser.ParseForFill(false, autofillView);
if (expectedPackageName != null)
Assert.Equal(expectedPackageName, result.PackageName);
if (expectedWebDomain != null)
Assert.Equal(expectedWebDomain, result.WebDomain);
foreach (var field in autofillView.InputFields)
{
string[] expectedHints = field.ExpectedAssignedHints;
if (expectedHints == null)
expectedHints = new string[0];
string[] actualHints;
parser.FieldsMappedToHints.TryGetValue(field, out actualHints);
if (actualHints == null)
actualHints = new string[0];
if (actualHints.Any() || expectedHints.Any())
{
_testOutputHelper.WriteLine($"field = {field.IdEntry} {field.Hint} {string.Join(",", field.AutofillHints ?? new string[]{})}");
_testOutputHelper.WriteLine("actual Hints = " + string.Join(", ", actualHints));
_testOutputHelper.WriteLine("expected Hints = " + string.Join(", ", expectedHints));
}
Assert.Equal(expectedHints.Length, actualHints.Length);
Assert.Equal(expectedHints.OrderBy(x => x), actualHints.OrderBy(x => x));
}
}
}
public class TestDalSourceTrustAll : IKp2aDigitalAssetLinksDataSource
{
public bool IsTrustedApp(string packageName)
{
return true;
}
public bool IsTrustedLink(string domain, string targetPackage)
{
return true;
}
public bool IsEnabled()
{
return true;
}
}
public class TestLogger : ILogger
{
public void Log(string x)
{
Console.WriteLine(x);
}
}
}

View File

@@ -0,0 +1,58 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<None Remove="chrome-android10-amazon-it.json" />
<None Remove="com-expressvpn-vpn-android13.json" />
<None Remove="com-ifs-banking-fiid3364-android13.json" />
<None Remove="com-servicenet-mobile-focused.json" />
<None Remove="com-servicenet-mobile-no-focus.json" />
<None Remove="firefox-amazon-it.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Kp2aAutofillParser\Kp2aAutofillParser.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="chrome-android10-amazon-it.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="com-expressvpn-vpn-android13.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="com-ifs-banking-fiid3364-android13.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="firefox-amazon-it.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="com-servicenet-mobile-focused.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="com-servicenet-mobile-no-focus.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1 @@
global using Xunit;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
{
"InputFields": [
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_bar_root",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_mode_bar_stub",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "layout",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "textView2",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "emailLayout",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "email",
"Hint": "E-Mail",
"ClassName": "android.widget.EditText",
"AutofillHints": [
"email"
],
"IsFocused": true,
"InputType": 33,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "emailAddress" ]
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "passwordLayout",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "password",
"Hint": "Passwort",
"ClassName": "android.widget.EditText",
"AutofillHints": [
"password",
"passwordAuto"
],
"IsFocused": false,
"InputType": 129,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [
"password",
"passwordAuto"
]
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "textinput_suffix_text",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "forgotPassword",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "amazonInfo",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "signInButtonBarrier",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "signIn",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "newUser",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "focusThief",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "activatingContainer",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
}
],
"PackageId": "com.expressvpn.vpn",
"WebDomain": null
}

View File

@@ -0,0 +1,322 @@
{
"InputFields": [
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_bar_root",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_mode_bar_stub",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "loginParent",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "rooted_device_error_screen",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "scroll",
"Hint": null,
"ClassName": "android.widget.ScrollView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "login_box_layout",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "loginFragment_container_view",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "login_box",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "Edt_UserId",
"Hint": "User ID",
"ClassName": "android.widget.EditText",
"AutofillHints": [
"passwordAuto"
],
"IsFocused": true,
"InputType": 145,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "username" ]
},
{
"IdEntry": "login_save_userid_switch",
"Hint": null,
"ClassName": "android.widget.CompoundButton",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "Edt_Password_layout",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "Edt_Password",
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": [
"passwordAuto"
],
"IsFocused": false,
"InputType": 129,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "password" ]
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "textinput_prefix_text",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "textinput_suffix_text",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "textinput_placeholder",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "forgot_login_btn",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "Btn_Login",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "login_fab_fragment_container",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "biometric_fragment_container",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "biometricLayout",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "login_menu_container",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "deposit_insurance_systems_textview",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "login_menu",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "login_menu_item_border_right",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "login_menu_item_border_left",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "sign_up_link",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "locations_link",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "more_link",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
}
],
"PackageId": "com.ifs.banking.fiid3364",
"WebDomain": null
}

View File

@@ -0,0 +1,121 @@
{
"InputFields": [
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": true,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_bar_root",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_mode_bar_stub",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "content",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "username_text_input_layout",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "username",
"Hint": "Username",
"ClassName": "android.widget.EditText",
"AutofillHints": null,
"IsFocused": true,
"InputType": 97,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "username" ]
},
{
"IdEntry": "password_text_input_layout",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "password",
"Hint": "Password",
"ClassName": "android.widget.EditText",
"AutofillHints": [
"passwordAuto"
],
"IsFocused": false,
"InputType": 129,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "password" ]
},
{
"IdEntry": "login_button",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "progressBar",
"Hint": null,
"ClassName": "android.widget.ProgressBar",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "forgot_password",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
}
],
"PackageId": "com.servicenet.mobile",
"WebDomain": null
}

View File

@@ -0,0 +1,119 @@
{
"InputFields": [
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": true,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_bar_root",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_mode_bar_stub",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "content",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "username_text_input_layout",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "username",
"Hint": "Username",
"ClassName": "android.widget.EditText",
"AutofillHints": null,
"IsFocused": false,
"InputType": 97,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "password_text_input_layout",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "password",
"Hint": "Password",
"ClassName": "android.widget.EditText",
"AutofillHints": [
"passwordAuto"
],
"IsFocused": false,
"InputType": 129,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null,
},
{
"IdEntry": "login_button",
"Hint": null,
"ClassName": "android.widget.Button",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "progressBar",
"Hint": null,
"ClassName": "android.widget.ProgressBar",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "forgot_password",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
}
],
"PackageId": "com.servicenet.mobile",
"WebDomain": null
}

View File

@@ -0,0 +1,469 @@
{
"InputFields": [
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_bar_root",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "action_mode_bar_stub",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "rootContainer",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "navigationToolbarStub",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "container",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "container",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "gestureLayout",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "browserWindow",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "browserLayout",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "swipeRefresh",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "engineView",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": true,
"InputType": 0,
"HtmlInfoTag": "",
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": "form",
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": [
"password"
],
"IsFocused": false,
"InputType": 225,
"HtmlInfoTag": "input",
"HtmlInfoTypeAttribute": "password",
"ExpectedAssignedHints": [ "password" ]
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": [
"emailAddress"
],
"IsFocused": false,
"InputType": 33,
"HtmlInfoTag": "input",
"HtmlInfoTypeAttribute": "email",
"ExpectedAssignedHints": [ "emailAddress" ]
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": "input",
"HtmlInfoTypeAttribute": "checkbox"
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": "input",
"HtmlInfoTypeAttribute": "submit"
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": "form",
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": [
"password"
],
"IsFocused": false,
"InputType": 225,
"HtmlInfoTag": "input",
"HtmlInfoTypeAttribute": "password",
"ExpectedAssignedHints": [ "password" ]
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": [
"emailAddress"
],
"IsFocused": false,
"InputType": 33,
"HtmlInfoTag": "input",
"HtmlInfoTypeAttribute": "email",
"ExpectedAssignedHints": [ "emailAddress" ]
},
{
"IdEntry": null,
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": "input",
"HtmlInfoTypeAttribute": "submit"
},
{
"IdEntry": "stubFindInPage",
"Hint": null,
"ClassName": "android.view.View",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "viewDynamicDownloadDialog",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "crash_reporter_view",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "toolbar",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_navigation_actions",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_origin_view",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_title_view",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_url_view",
"Hint": "Suche oder Adresse",
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_page_actions",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_browser_actions",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "counter_root",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "counter_text",
"Hint": null,
"ClassName": "android.widget.TextView",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_menu",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_progress",
"Hint": null,
"ClassName": "android.widget.ProgressBar",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_container",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_edit_actions_start",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_edit_url_view",
"Hint": null,
"ClassName": "android.widget.EditText",
"AutofillHints": null,
"IsFocused": false,
"InputType": 17,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "mozac_browser_toolbar_edit_actions_end",
"Hint": null,
"ClassName": "android.widget.LinearLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "readerViewControlsBar",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "addressSelectBar",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "creditCardSelectBar",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "loginSelectBar",
"Hint": null,
"ClassName": "android.view.ViewGroup",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
},
{
"IdEntry": "tabPreview",
"Hint": null,
"ClassName": "android.widget.FrameLayout",
"AutofillHints": null,
"IsFocused": false,
"InputType": 0,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
}
],
"PackageId": "org.mozilla.firefox",
"WebDomain": "www.amazon.it"
}

View File

@@ -51,7 +51,12 @@ public class SftpUserInfo implements UserInfo {
intent.putExtra("keepass2android.sftp.prompt", text);
intent.setData((Uri.parse("suckit://"+SystemClock.elapsedRealtime())));
PendingIntent contentIntent = PendingIntent.getActivity(_appContext, 0, intent, 0);
int flags = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
flags |= PendingIntent.FLAG_MUTABLE;
}
PendingIntent contentIntent = PendingIntent.getActivity(_appContext, 0, intent, flags);
builder.setContentIntent(contentIntent);
{

View File

@@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1536m

View File

@@ -0,0 +1 @@
include ':app'

View File

@@ -19,4 +19,4 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2048m
org.gradle.jvmargs=-Xmx1536m

View File

@@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1536m

View File

@@ -25,7 +25,7 @@
<!-- Title for Latin keyboard input options dialog -->
<string name="english_ime_input_options">Eingabeoptionen</string>
<!-- Option to provide vibrate/haptic feedback on keypress -->
<string name="vibrate_on_keypress">Vibrieren b. Tastendruck</string>
<string name="vibrate_on_keypress">Bei Tastendruck vibrieren</string>
<!-- Option to play back sound on keypress in soft keyboard -->
<string name="sound_on_keypress">Ton bei Tastendruck</string>
<!-- Option to pop up the character with a larger font above soft keyboard -->
@@ -120,13 +120,13 @@
<!-- Tutorial tip 2 - Touch and hold a key to view accents (examples) -->
<string name="tip_to_view_accents"><b>\"Halten Sie eine Taste gedrückt, um Akzente anzuzeigen\"\n\"(ø, ö, ô, ó usw.).\"</b></string>
<!-- Tutorial tip 3 - How to switch to number/symbol keyboard -->
<string name="tip_to_open_symbols"><b>\"Wechseln Sie zu Ziffern und Symbolen, indem Sie diese Taste berühren.\"</b></string>
<string name="tip_to_open_symbols"><b>Wechseln Sie zu Ziffern und Symbolen, indem Sie diese Taste berühren.</b></string>
<!-- Tutorial tip 4 - How to switch back to alphabet keyboard -->
<string name="tip_to_close_symbols"><b>\"Durch erneutes Drücken dieser Taste gelangen Sie zurück zu den Buchstaben.\"</b></string>
<string name="tip_to_close_symbols"><b>Durch erneutes Drücken dieser Taste gelangen Sie zurück zu den Buchstaben.</b></string>
<!-- Tutorial tip 5 - How to launch keyboard settings -->
<string name="tip_to_launch_settings"><b>\"Halten Sie diese Taste gedrückt, um die Tastatureinstellungen, wie beispielsweise die automatische Vervollständigung, zu ändern.\"</b></string>
<string name="tip_to_launch_settings"><b>Halten Sie diese Taste gedrückt, um die Tastatureinstellungen, wie beispielsweise die automatische Vervollständigung, zu ändern.</b></string>
<!-- Tutorial tip 6 - Done with the tutorial -->
<string name="tip_to_start_typing"><b>\"Probieren Sie es aus!\"</b></string>
<string name="tip_to_start_typing"><b>Probieren Sie es aus!</b></string>
<!-- Label for soft enter key when it performs GO action. Must be short to fit on key! -->
<string name="label_go_key">Los</string>
<!-- Label for soft enter key when it performs NEXT action. Must be short to fit on key! -->
@@ -166,7 +166,7 @@
<string name="voice_working">Vorgang läuft</string>
<!-- Short message shown before the user should speak. -->
<!-- Short message shown when a generic error occurs. -->
<string name="voice_error">Fehler. Versuchen Sie es erneut..</string>
<string name="voice_error">Fehler. Versuchen Sie es erneut.</string>
<!-- Short message shown for a network error. -->
<string name="voice_network_error">Keine Verbindung</string>
<!-- Short message shown for a network error where the utterance was really long,
@@ -184,9 +184,9 @@
search is not installed. -->
<string name="voice_not_installed">Sprachsuche nicht installiert</string>
<!-- Short hint shown in candidate view to explain voice input. -->
<string name="voice_swipe_hint"><b>\"Hinweis:\"</b>\" Ziehen Sie zum Sprechen den Finger über die Tastatur.\"</string>
<string name="voice_swipe_hint"><b>Hinweis:</b> Ziehen Sie zum Sprechen den Finger über die Tastatur.</string>
<!-- Short hint shown in candidate view to explain that user can speak punctuation. -->
<string name="voice_punctuation_hint"><b>\"Hinweis:\"</b>\" Versuchen Sie beim nächsten Mal, Satzzeichen wie \"Punkt\", \"Komma\" oder \"Fragezeichen\" per Sprachbefehl einzugeben.\"</string>
<string name="voice_punctuation_hint"><b>Hinweis:</b> Versuchen Sie beim nächsten Mal, Satzzeichen wie Punkt“, „Komma oder Fragezeichen per Sprachbefehl einzugeben.</string>
<!-- Label on button to stop recognition. Must be short to fit on button. -->
<string name="cancel">Abbrechen</string>
<!-- Label on button when an error occurs -->
@@ -214,13 +214,13 @@
<string name="auto_submit_summary">Drücken Sie auf die Eingabetaste, wenn Sie einen Suchvorgang durchführen oder zum nächsten Feld wechseln.</string>
<!-- IME Tutorial screen (ROMAN) -->
<!-- appears above image showing the user to click on a TextView to show the IME -->
<string name="open_the_keyboard"><font size="17"><b>\"Tastatur öffnen\"\n</b></font><font size="3">\n</font>\"Berühren Sie ein beliebiges Textfeld.\"</string>
<string name="open_the_keyboard"><font size="17"><b>Tastatur öffnen\n</b></font><font size="3">\n</font>Berühren Sie ein beliebiges Textfeld.</string>
<!-- appears above the image showing the back button used to close the keyboard -->
<string name="close_the_keyboard"><font size="17"><b>\"Tastatur schließen\"\n</b></font><font size="3">\n</font>\"Drücken Sie die Zurücktaste.\"</string>
<string name="close_the_keyboard"><font size="17"><b>Tastatur schließen\n</b></font><font size="3">\n</font>Drücken Sie die Zurücktaste.</string>
<!-- appears above image showing how to use touch and hold -->
<string name="touch_and_hold"><font size="17"><b>\"Für Optionen eine Taste berühren und gedrückt halten\"\n</b></font><font size="3">\n</font>\"Greifen Sie auf Satzzeichen und Akzente zu.\"</string>
<string name="touch_and_hold"><font size="17"><b>Für Optionen eine Taste berühren und gedrückt halten\n</b></font><font size="3">\n</font>Greifen Sie auf Satzzeichen und Akzente zu.</string>
<!-- appears above image showing how to access keyboard settings -->
<string name="keyboard_settings"><font size="17"><b>\"Tastatureinstellungen\"\n</b></font><font size="3">\n</font>\"Berühren und halten Sie die Taste \"<b>\"?123\"</b>\" gedrückt.\"</string>
<string name="keyboard_settings"><font size="17"><b>Tastatureinstellungen\n</b></font><font size="3">\n</font>Berühren und halten Sie die Taste <b>\"?123\"</b> gedrückt.</string>
<!-- popular web domains for the locale - most popular, displayed on the keyboard -->
<string name="popular_domain_0">".com"</string>
<!-- popular web domains for the locale - item 1, displayed in the popup -->

View File

@@ -81,7 +81,7 @@
<!-- Option to always show the settings key -->
<string name="settings_key_mode_always_show_name">一律顯示</string>
<!-- Option to always hide the settings key -->
<string name="settings_key_mode_always_hide_name">永遠隱藏</string>
<string name="settings_key_mode_always_hide_name">一律隱藏</string>
<!-- Array of the settings key modes -->
<string-array name="settings_key_modes">
<item>@string/settings_key_mode_auto_name</item>

View File

@@ -1 +1 @@
org.gradle.jvmargs=-Xmx1024m
org.gradle.jvmargs=-Xmx1536m

View File

@@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1536m

View File

@@ -1 +1 @@
org.gradle.jvmargs=-Xmx1024m
org.gradle.jvmargs=-Xmx1536m

View File

@@ -56,15 +56,15 @@
<string name="afc_title_sort_by">Sortieren nach…</string>
<string name="afc_yesterday">Gestern</string>
<plurals name="afc_title_choose_directories">
<item quantity="one">Verzeichnis wählen&#8230;</item>
<item quantity="one">Ordner wählen …</item>
<item quantity="other">Verzeichnisse wählen&#8230;</item>
</plurals>
<plurals name="afc_title_choose_files">
<item quantity="one">Datei wählen&#8230;</item>
<item quantity="other">Dateien wählen&#8230;</item>
<item quantity="other">Dateien wählen</item>
</plurals>
<plurals name="afc_title_choose_files_directories">
<item quantity="one">Datei/Ordner wählen&#8230;</item>
<item quantity="other">Dateien/Ordner wählen&#8230;</item>
<item quantity="one">Datei/Ordner wählen</item>
<item quantity="other">Dateien/Ordner wählen</item>
</plurals>
</resources>

View File

@@ -1,2 +1,2 @@
org.gradle.jvmargs=-Xmx1024m
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="189"
android:versionName="1.09e-r1"
android:versionCode="192"
android:versionName="1.09e-r4"
package="keepass2android.keepass2android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="189"
android:versionName="1.09e-r1"
android:versionCode="192"
android:versionName="1.09e-r4"
package="keepass2android.keepass2android_nonet"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background_blue"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background_blue"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background_green"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background_green"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -5,15 +5,15 @@
<string name="AboutText">Keepass2Android er en adgangskodehåndterings-app, der giver læse-/skriveadgang til KeePass 2.x-databaser på Android.</string>
<string name="CreditsText">Brugerfladen er baseret på en portering af KeePassDroid, udviklet af Brian Pellin. Koden til databaseoperationerne baserer sig på KeePass af Dominik Reichl. Android-robotten er reproduceret eller ændret fra arbejde skabt og delt af Google og anvendt jf. betingelserne beskrevet i \"Creative Commons 3.0 Attribution License\".</string>
<string name="CreditsTextSFTP">SFTP-understøttelse er implementeret vha. JSch-biblioteket under BSD-licensen, skabt af JCraft, Inc.</string>
<string name="CreditsIcons">Hammerikonet er kreéret af John Caserta fra Noun Project. Pingvinikonet er kreéret af Adriano Emerick fra Noun Project. Fjerikonet er kreéret af Jon Testa fra Noun Project. Æbleikonet er kreéret af Ava Rowell fra Noun Project. Billedikonet stammer fra https://icons8.com/icon/5570/Picture.</string>
<string name="CreditsIcons">Hammerikonet er lavet af John Caserta fra Noun Project. Pingvinikonet er lavet af Adriano Emerick fra Noun Project. Fjerikonet er lavet af Jon Testa fra Noun Project. Æbleikonet er lavet af Ava Rowell fra Noun Project. Billedikonet er fra https://icons8.com/icon/5570/Picture.</string>
<string name="accept">Acceptér</string>
<string name="deny">Afvis</string>
<string name="add_entry">Tilføj post</string>
<string name="edit_entry">Redigér post</string>
<string name="add_url_entry">Opret indtastning for URL</string>
<string name="add_url_entry">Opret post for webadresse</string>
<string name="add_group">Tilføj gruppe</string>
<string name="add_group_title">Tilføj Gruppe</string>
<string name="edit_group_title">Redigér gruppe</string>
<string name="edit_group_title">Rediger gruppe</string>
<string name="algorithm">Algoritme</string>
<string name="algorithm_colon">Algoritme</string>
<string name="app_name">Keepass2Android</string>
@@ -32,31 +32,31 @@
<string name="NavigationToGroupCompleted_message">Visningsgruppe er nu: %1$s</string>
<string name="AutofillDisabledQueriesPreference_title">Deaktivér Autoudfyldmål</string>
<string name="AutofillDisabledQueriesPreference_summary">Viser en liste over apps og websteder, hvor Autoudfyld er blevet deaktiveret</string>
<string name="OfferSaveCredentials_summary">Hvis aktiveret, spørger Android, om du vil gemme akkreditiver, når du manuelt har angivet data i autofyldbare felter.</string>
<string name="OfferSaveCredentials_summary">Hvis aktiveret spørger Android, om du vil gemme akkreditiver, når du manuelt har angivet data i autofyldbare felter.</string>
<string name="OfferSaveCredentials_title">Tilbyd at gemme akkreditiver</string>
<string name="ShowGroupInEntry_title">Vis gruppenavn i indtastningsvisning</string>
<string name="unknown_uri_scheme">Beklager! Keepass2Android kan ikke håndtere den returnerede URI %1$s. Kontakt udvikleren!</string>
<string name="unknown_uri_scheme">Beklager! Keepass2Android kan ikke håndtere den returnerede URI %1$s. Kontakt venligst udvikleren!</string>
<string name="Entry_singular">Én indtastning</string>
<string name="Entry_plural">%1$d poster</string>
<string name="IconSet_title">Ikonsæt</string>
<string name="IconSet_install">Find flere...</string>
<string name="security_prefs">Sikkerhed</string>
<string name="display_prefs">Visning</string>
<string name="password_access_prefs">Adgangskodetilgang</string>
<string name="password_access_prefs">Adgang til adgangskoden</string>
<string name="QuickUnlock_prefs">Hurtigoplåsning</string>
<string name="FileHandling_prefs">Filhåndtering</string>
<string name="keyboard_prefs">Tastatur</string>
<string name="export_prefs">Eksportere database...</string>
<string name="export_prefs">Eksporter database</string>
<string name="fingerprint_prefs">Biometrisk oplåsning</string>
<string name="import_db_prefs">Importere database til intern mappe</string>
<string name="import_db_prefs">Importer database til intern mappe</string>
<string name="import_keyfile_prefs">Importer nøglefilen til intern mappe</string>
<string name="export_keyfile_prefs">Eksporter nøglefil fra intern mappe</string>
<string name="keyboardswitch_prefs">Tastaturskiftning</string>
<string name="keyboardswitch_prefs">Tastaturskifte</string>
<string name="OnlyAvailableForLocalFiles">Kun tilgængelig for lokale filer.</string>
<string name="FileIsInInternalDirectory">Fil lagres i den interne mappe.</string>
<string name="DatabaseFileMoved">Databasefilen blev kopieret til den interne mappe. Tryk OK for at åbne den fra den nye placering. Bemærk: Husk regelmæssigt at eksportere databasen til et sikkert lagermedie!</string>
<string name="KeyfileMoved">Nøglefilen blev kopieret til den interne mappe. Inden du sletter den fra dens nuværende placering, så tjek at du har en god sikkerhedskopi!</string>
<string name="KeyfileMoveRequiresRememberKeyfile">Kan ikke benytte den interne mappe, når nøglefilens placering ikke er husket. Ændr sikkerhedsindstillingerne.</string>
<string name="FileIsInInternalDirectory">Fil er gemt i intern mappe.</string>
<string name="DatabaseFileMoved">Databasefil blev kopieret til intern mappe. Tryk OK for at åbne fra den nye placering. Bemærk: Husk regelmæssigt at eksportere databasen til et sikkert lagermedie!</string>
<string name="KeyfileMoved">Nøglefil blev kopieret til interne mappe. Før du sletter den fra den nuværende placering, så tjek at du har en god sikkerhedskopi!</string>
<string name="KeyfileMoveRequiresRememberKeyfile">Kan ikke bruge intern mappe når nøglefilens placering ikke er husket. Ændr sikkerhedsindstillingerne.</string>
<string name="unlock_database_button">Oplås</string>
<string name="unlock_database_title">Oplås database</string>
<string name="brackets">Parenteser</string>
@@ -73,8 +73,8 @@
<string name="available_through_keyboard">Post er tilgængelig via KP2A-tastaturet</string>
<string name="app_language_pref_title">App-sprog</string>
<string name="entry_is_available">er tilgængelig</string>
<string name="not_possible_im_picker">Kunne ikke åbne dialogboksen til valg af inputmetode. Aktivér tastaturet manuelt.</string>
<string name="please_activate_keyboard">Aktivér Keepass2Android-tastaturet i systemindstillingerne.</string>
<string name="not_possible_im_picker">Kunne ikke åbne dialogboksen for valg af inputmetode. Aktiver venligst tastaturet manuelt.</string>
<string name="please_activate_keyboard">Aktiver venligst Keepass2Android-tastaturet i systemindstillingerne.</string>
<string name="creating_db_key">Opretter databasenøgle…</string>
<string name="current_group">Aktuel Gruppe</string>
<string name="current_group_root">Aktuel gruppe: Root</string>
@@ -82,7 +82,7 @@
<string name="digits">Cifre</string>
<string name="disclaimer_formal">Keepass2Android leveres ABSOLUT UDEN GARANTI. Det er gratis software, og du er velkommen til at videredistribuere det jf. betingelserne i GPL version 2 eller senere.</string>
<string name="ellipsis">\u2026</string>
<string name="copy_to_clipboard">Kopiér til Upklipsholder</string>
<string name="copy_to_clipboard">Kopiér til udklipsholder</string>
<string name="SystemLanguage">Systemsprog</string>
<string name="fingerprint_description">Verificér for at fortsætte</string>
<string name="fingerprint_fatal">Kan ikke opsætte biometrisk oplåsning:</string>
@@ -90,20 +90,20 @@
<string name="fingerprint_success">Biometrisk verifikation lykkedes</string>
<string name="fingerprint_os_error">Biometrisk oplåsning kræver Android 6.0 eller nyere.</string>
<string name="fingerprint_hardware_error">Ingen biometrisk hardware fundet.</string>
<string name="fingerprint_no_enrolled">Du har ikke konfigureret biometrisk verifikation på denne enhed. Gå til systemindstillinger.</string>
<string name="fingerprint_no_enrolled">Du har ikke konfigureret biometrisk verifikation på denne enhed. Gå til systemindstillinger først.</string>
<string name="disable_fingerprint_unlock">Deaktivér biometrisk oplåsning</string>
<string name="enable_fingerprint_unlock">Aktivér fuld biometrisk oplåsning</string>
<string name="enable_fingerprint_quickunlock">Aktivér biometrisk oplåsning for hurtig oplåsning</string>
<string name="fingerprint_unlock_failed">Biometrisk oplåsning mislykkedes. Dekrypteringsnøglen blev ugyldiggjort af Android OS\'et. Dette sker sædvanligvis, hvis en biometrik godkendelse eller sikkerhedsindstillingerne ændres. </string>
<string name="fingerprint_disabled_wrong_masterkey">Databaseoplåsning mislykkedes: Ugyldig kombinøgle. Biometrisk oplåsning blev deaktiveret, da den lagrede hovedadgangskode tilsyneladende ikke længere er gyldig. </string>
<string name="fingerprint_reenable">Genaktivér biometrisk oplåsning for den nye hovedadgangskode.</string>
<string name="fingerprint_unlock_failed">Biometrisk oplåsning mislykkedes. Dekrypteringsnøglen blev ugyldiggjort af Android OS. Det sker normalt, hvis en biometrik godkendelse eller sikkerhedsindstillinger ændres.</string>
<string name="fingerprint_disabled_wrong_masterkey">Databaseoplåsning mislykkedes: Ugyldig kombinøgle. Biometrisk oplåsning blev deaktiveret, da den lagrede hovedadgangskode tilsyneladende ikke længere er gyldig.</string>
<string name="fingerprint_reenable">Genaktivér venligst biometrisk oplåsning for den nye hovedadgangskode.</string>
<string name="fingerprint_reenable2">Oplås med din adgangskode og genaktivér så biometrisk oplåsning i databaseindstillingerne.</string>
<string name="FingerprintInitFailed">Kunne ikke initialisere biometrisk verifikation. </string>
<string name="FingerprintSetupFailed">Mislykkedes at kryptere data. Dette kan ske, hvis du tilføjer eller fjerner fingeraftryk i systemindstillingerne, mens Keepass2Android moniterer for brug af fingeraftryk.</string>
<string name="enable_fingerprint_unlock_Info">Dette gemmer din hovedadgangskode på denne enhed, krypteret med Android Keystore og beskyttet af biometrisk verifikation. Tillader dig at oplåse din database alene via biometri.</string>
<string name="FingerprintInitFailed">Kunne ikke initialisere biometrisk verifikation.</string>
<string name="FingerprintSetupFailed">Kryptering af data fejlede. Dette kan ske, hvis du tilføjer eller fjerner fingeraftryk i systemindstillingerne, mens Keepass2Android moniterer for brug af fingeraftryk.</string>
<string name="enable_fingerprint_unlock_Info">Dette gemmer hovedadgangskoden på denne enhed, krypteret med Android Keystore og beskyttet af biometrisk verifikation. Tillader oplåsning af databasen alene med biometri.</string>
<string name="enable_fingerprint_quickunlock_Info">Tillader brug af biometrisk verifikation i stedet for hurtigoplåsningskoden. Gemmer ingen information relateret til din hovedadgangskode.</string>
<string name="enter_filename">Angiv databasefilnavn</string>
<string name="entry_accessed">Tilgået</string>
<string name="enter_filename">Angiv navn på databasefil</string>
<string name="entry_accessed">Åbnet</string>
<string name="entry_cancel">Annullér</string>
<string name="entry_comment">Notater</string>
<string name="entry_tags">Tags</string>
@@ -120,27 +120,27 @@
<string name="entry_title">Navn</string>
<string name="entry_url">URL</string>
<string name="entry_user_name">Brugernavn</string>
<string name="entry_extra_strings">Ekstra strengfelter</string>
<string name="entry_binaries">Filvedhæftninger</string>
<string name="entry_extra_strings">Ekstra felter</string>
<string name="entry_binaries">Vedhæftede filer</string>
<string name="entry_history">Tidligere versioner</string>
<string name="error_can_not_handle_uri">Keepass2Android kan ikke håndtere denne URI.</string>
<string name="error_could_not_create_group">Fejl under gruppeoprettelse.</string>
<string name="error_could_not_create_group">Fejl under oprettelse af gruppe.</string>
<string name="error_could_not_create_parent">Overordnet mappe kunne ikke oprettes.</string>
<string name="error_database_exists">Filen findes allerede.</string>
<string name="error_database_exists">Denne fil eksisterer allerede.</string>
<string name="error_database_settinoverrgs">Mislykkedes at bestemme databaseindstillinger.</string>
<string name="error_failed_to_launch_link">Mislykkedes at åbne link.</string>
<string name="error_filename_required">Et filnavn er obligatorisk.</string>
<string name="error_filename_required">Et filnavn er påkrævet.</string>
<string name="error_file_not_create">Kunne ikke oprette fil</string>
<string name="error_invalid_db">Ugyldig database.</string>
<string name="error_invalid_path">Ugyldig sti.</string>
<string name="error_no_name">Et navn er obligatorisk.</string>
<string name="error_nopass">En adgangskode eller nøglefil er obligatorisk.</string>
<string name="error_no_name">Et navn er påkrævet.</string>
<string name="error_nopass">En adgangskode eller nøglefil er påkrævet.</string>
<string name="error_pass_gen_type">Mindst én adgangskodegenereringstype skal vælges</string>
<string name="error_pass_match">Adgangskoder matcher ikke.</string>
<string name="error_pass_match">Adgangskoder stemmer ikke overens.</string>
<string name="error_rounds_not_number">Gentagelser skal udgøre et tal.</string>
<string name="error_param_not_number">Parameter skal udgøre et tal.</string>
<string name="error_title_required">En titel er obligatorisk.</string>
<string name="error_wrong_length">Angiv et positivt heltal i længdefeltet</string>
<string name="error_title_required">En titel er påkrævet.</string>
<string name="error_wrong_length">Angiv et positivt helt tal i længdefeltet</string>
<string name="FileNotFound">Fil ikke fundet.</string>
<string name="file_browser">Filbrowser</string>
<string name="generate_password">Generér adgangskode</string>
@@ -216,7 +216,7 @@
<string name="master_key_type">Vælg type af hovednøgle:</string>
<string name="progress_create">Opretter ny database…</string>
<string name="create_database">Opret database</string>
<string name="progress_title">Behandler…</string>
<string name="progress_title">Arbejder…</string>
<string name="remember_keyfile_summary">Husker placeringen af nøglefiler</string>
<string name="remember_keyfile_title">Gem nøglefil</string>
<string name="remove_from_filelist">Fjern</string>
@@ -228,7 +228,7 @@
<string name="KeyDerivFunc">Nøgleafledningsfunktion</string>
<string name="rounds">Krypteringsgentagelser</string>
<string name="rounds_explaination">Flere krypteringsgentagelser giver øget beskyttelse imod brute force-angreb, men kan reduceredownload- og lagringstigheden mærkbart.</string>
<string name="rounds_hint">repetitioner</string>
<string name="rounds_hint">gentagelser</string>
<string name="argon2memory">Hukommelse til Argon 2 (bytes)</string>
<string name="argon2parallelism">Parallelisme til Argon 2</string>
<string name="database_name">Databasenavn</string>
@@ -240,10 +240,10 @@
<string name="space">Mellemrum</string>
<string name="search_label">Søg</string>
<string name="show_password">Vis adgangskode</string>
<string name="sort_menu">Sortér efter...</string>
<string name="sort_name">Sortér efter navn</string>
<string name="sort_menu">Sorter efter...</string>
<string name="sort_name">Sorter efter navn</string>
<string name="sort_db">Sortér efter oprettelsestidspunkt</string>
<string name="sort_moddate">Sortér efter ændringsdato</string>
<string name="sort_moddate">Sorter efter ændringsdato</string>
<string name="sort_default">Behold standardrækkefølgen</string>
<string name="special">Speciel</string>
<string name="special_extended">Udvidet Speciel</string>
@@ -685,6 +685,12 @@
<string name="EntryChannel_desc">Notificering til forenkelse af adgang til den aktuelt valgte indtastning.</string>
<string name="CloseDbAfterFailedAttempts">Luk database efter tre mislykkede forsøg med biometrisk oplåsning.</string>
<string name="WarnFingerprintInvalidated">Advarsel! Biometrisk godkendelse kan ugyldiggøres af Android, f.eks. efter tilføjelse af et nyt fingeraftryk i dine enhedsindstillinger. Sørg for, at du altid ved, hvordan du låser op med din hovedadgangskode!</string>
<string-array name="ChangeLog_1_09e">
<item>Fejlrettelse til nedbrud og uventede log-outs</item>
<item>Skift til ny SFTP-implementering, som understøtter moderne offentlige nøglealgoritmer såsom rsa-sha2-256</item>
<item>Markér adgangskoder som følsomme ved kopiering til udklipsholder (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>Tilføjet understøttelse af visning, fjernelse og gendannelse af sikkerhedskopierede poster</item>
<item>Implementeret understøttelse af MEGA-skylager</item>

File diff suppressed because it is too large Load Diff

View File

@@ -301,6 +301,8 @@
<string name="NoDalVerification_summary">Απενεργοποιεί τον έλεγχο αν ταιριάζει ο τομέας και το πακέτο εφαρμογής</string>
<string name="InlineSuggestions_title">Ενσωμάτωση με πληκτρολόγιο</string>
<string name="InlineSuggestions_summary">Δείχνει τις προτάσεις αυτόματης συμπλήρωσης ως γραμμές μέσα στο πρηκτρολόγιο</string>
<string name="LogAutofillView_title">Προβολή αυτόματης συμπλήρωσης αρχείου καταγραφής</string>
<string name="LogAutofillView_summary">Εγγραφή λεπτομερειών σχετικά με την προβολή αυτόματης συμπλήρωσης στο αρχείο καταγραφής αποσφαλμάτωσης (αν η καταγραφή αποσφαλμάτωσης είναι ενεργοποιημένη). Αυτές οι λεπτομέρειες μπορούν να σταλούν στον προγραμματιστή αν η αυτόματη συμπλήρωση δε λειτουργεί όπως αναμενόταν.</string>
<string name="requires_android11">Απαιτείται Android 11 ή νεότερη έκδοση</string>
<string name="kp2a_findUrl">Εύρεση συνθηματικού</string>
<string name="excludeExpiredEntries">Εξαίρεση ληγμένων εγγραφών</string>
@@ -682,6 +684,12 @@
<string name="EntryChannel_desc">Ειδοποίηση για απλοποιημένη πρόσβαση στην τρέχουσα καταχώριση.</string>
<string name="CloseDbAfterFailedAttempts">Κλείσιμο της βάσης δεδομένων μετά από 3 ανεπιτυχείς προσπάθειες βιομετρικού ξεκλειδώματος.</string>
<string name="WarnFingerprintInvalidated">Προσοχή! Ο βιομετρικός έλεγχος ταυτότητας μπορεί να ακυρωθεί από το Android, π.χ. μετά την προσθήκη ενός νέου δακτυλικού αποτυπώματος στις ρυθμίσεις της συσκευής σας. Βεβαιωθείτε ότι ξέρετε πάντα πώς να ξεκλειδώσετε με τον κύριο κωδικό πρόσβασης!</string>
<string-array name="ChangeLog_1_09e">
<item>Διόρθωση σφάλματος για απότομα κλεισίματα εφαρμογής και μη αναμενόμενες αποσυνδέσεις</item>
<item>Μετάβαση σε νέα υλοποίηση SFTP, υποστηρίζοντας σύγχρονους αλγόριθμους δημόσιου κλειδιού όπως rsa-sha2-256</item>
<item>Μαρκάρισμα κωδικών πρόσβασης ως ευαίσθητοι κατά την αντιγραφή στο πρόχειρο (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>Υποστηρίζεται πλέον προβολή, διαγραφή και ανάκτηση των διαγραμμένων εγγραφών</item>
<item>Υλοποιήθηκε υποστήριξη για αποθήκευση στο νέφος MEGA </item>

View File

@@ -2,6 +2,7 @@
<!--Generated by crowdin.net-->
<resources>
<string name="about_feedback">Iruzkinak</string>
<string name="AboutText">Keepass2Android pasahitz kudeatzaile bat da, Androiden KeePass 2.x datu-baseetan irakurketa/idazketa ahalbideratzen duena. </string>
<string name="accept">Onartu</string>
<string name="deny">Ukatu</string>
<string name="add_entry">Gehitu sarrera</string>
@@ -17,8 +18,10 @@
<string name="app_name_nonet">Keepass2Android Offline</string>
<string name="short_app_name_nonet">KP2A Offline</string>
<string name="app_timeout">Aplikazioa denboraz kanpo</string>
<string name="app_timeout_summary">Aplikazioa inaktibo dagoenean datu-basea blokeatzeko denbora.</string>
<string name="kill_app_label">Aplikazioaren prozesua hil</string>
<string name="show_kill_app">Itxi-botoia</string>
<string name="show_kill_app_summary">Erakutsi botoi bat pasahitzaren pantailan aplikazioaren prozesua hiltzeko (erabiltzaile paranoikoentzat)</string>
<string name="application">Aplikazioa</string>
<string name="application_settings">Aplikazioaren ezarpenak</string>
<string name="ShowGroupnameInSearchResult_title">Erakutsi taldearen izena bilaketaren emaitzan</string>

View File

@@ -687,6 +687,7 @@ Voici quelques conseils qui pourraient aider à diagnostiquer le problème : \n
<item>Correction de bugs pour les plantages et les déconnexions inattendues</item>
<item>Passage à une nouvelle implémentation SFTP, prenant en charge les algorithmes à clé publique modernes tels que rsa-sha2-256</item>
<item>Marquer les mots de passe comme sensibles lors de la copie dans le presse-papiers (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>Ajout du support pour la visualisation, la suppression et la restauration des sauvegardes d\'entrée</item>

View File

@@ -37,7 +37,7 @@
<string name="ShowGroupInEntry_title">エントリー画面にグループ名を表示</string>
<string name="unknown_uri_scheme">申し訳ありません! Keepass2Android は、返された URI %1$s を処理できませんでした。開発者にお問い合わせください!</string>
<string name="Entry_singular">1 エントリー</string>
<string name="Entry_plural">%1$d エントリー</string>
<string name="Entry_plural">%1$d 件のエントリー</string>
<string name="IconSet_title">アイコンセット</string>
<string name="IconSet_install">さらに探す…</string>
<string name="security_prefs">セキュリティ</string>
@@ -168,7 +168,7 @@
<string name="list_size_title">グループ一覧のサイズ</string>
<string name="list_size_summary">グループ一覧の文字の大きさ</string>
<string name="loading_database">データベースを読み込み中…</string>
<string name="lowercase">小文字</string>
<string name="lowercase">小文字</string>
<string name="MaskedPassword">*****</string>
<string name="maskpass_title">パスワードを隠す</string>
<string name="maskpass_summary">デフォルトでパスワード欄を非表示にします</string>
@@ -280,8 +280,8 @@
<string name="version_label">バージョン</string>
<string name="version_history">バージョン履歴</string>
<string name="author">Keepass2Android は Philipp Crocoll によって開発されています。</string>
<string name="further_authors">%1$s のコードへの貢献に感謝します。</string>
<string name="designers">%1$s のアイコンとレイアウトデザインへの貢献に感謝します。</string>
<string name="further_authors">%1$s 諸氏のコードへの貢献に感謝します。</string>
<string name="designers">%1$s 諸氏のアイコンとレイアウトデザインへの貢献に感謝します。</string>
<string name="supporters">%1$s による財政面の支援に感謝します。</string>
<string name="credit_plugin1">KP2A には Scott Greenberg 氏により開発された KeePass 向け Twofish 暗号化プラグインが含まれています。</string>
<string name="credit_android_filechooser">android-filechooser は Hai Bison 氏によって開発されました。</string>
@@ -389,9 +389,9 @@
<string name="AcceptAllServerCertificates_summary">証明書の検証に失敗したときの動作を指定します。注意: 検証に失敗したときは、デバイスに証明書をインストールすることができます。</string>
<string name="ClearOfflineCache_title">キャッシュをクリアしますか?</string>
<string name="ClearOfflineCache_question">すべてのキャッシュされたデータベースファイルを削除します。元のデータベースにアクセスできない間に行った、同期されていない変更がすべて失われます! 続行しますか?</string>
<string name="CheckForFileChangesOnSave_title">変更をチェック</string>
<string name="CheckForFileChangesOnSave_title">変更を確認</string>
<string name="CheckForFileChangesOnSave_summary">変更を保存する前に、ファイルが外部から変更されているか確認します。</string>
<string name="CheckForDuplicateUuids_title">UUID の重複をチェック</string>
<string name="CheckForDuplicateUuids_title">UUID の重複を確認</string>
<string name="CheckForDuplicateUuids_summary">同じ ID で複数のエントリーがあるかによってデータベース ファイルの破損を確認します。これは予期しない動作が発生する可能性があります。</string>
<string name="ShowCopyToClipboardNotification_title">クリップボード通知</string>
<string name="ShowCopyToClipboardNotification_summary">ユーザー名とパスワードが、通知バーとクリップボードからアクセス可能になります。パスワード盗聴アプリに気をつけてください!</string>
@@ -482,7 +482,7 @@
<string name="otp_aux_file">OTP 補助ファイル</string>
<string name="ErrorOcurred">エラーが発生しました:</string>
<string name="DuplicateUuidsError">データベースが破損しています: 重複する ID が見つかりました。(Minikeepass で保存しましたか?) PC 用 Keepass 2 で新しいデータベースに再インポートして「新しい ID の作成」を選択してください。</string>
<string name="DuplicateUuidsErrorAdditional">設定/アプリ設定/ファイル処理/UUID の重複をチェック で、このエラーメッセージを無効にできます。予期しない動作が発生する可能性があることにご注意ください。データベースを修正することをお勧めします。</string>
<string name="DuplicateUuidsErrorAdditional">設定/アプリ設定/ファイル処理/UUID の重複を確認 で、このエラーメッセージを無効にできます。予期しない動作が発生する可能性があることにご注意ください。データベースを修正することをお勧めします。</string>
<string name="synchronize_database_menu">データベースを同期...</string>
<string name="CannotMoveGroupHere">このグループにグループを移動できません。</string>
<string name="donate_question">今日はオクトーバーフェスト! Keepass2Android を気に入ってくださったなら、今日は私にビールを買っていただくのにいい日ではありませんか?</string>
@@ -690,6 +690,7 @@
<item>クラッシュと突発的にログアウトするバグを修正</item>
<item>rsa-sha2-256 などの最新の公開鍵アルゴリズムをサポートする、新しい SFTP 実装に切り替え</item>
<item>パスワードをクリップボードにコピーしたとき、機密コンテンツとしてマークするよう変更 (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>エントリーバックアップの表示、削除、および復元に対応</item>

View File

@@ -687,9 +687,10 @@
<string name="CloseDbAfterFailedAttempts">Zamknij bazę danych po trzech nieudanych próbach odblokowania biometrycznego.</string>
<string name="WarnFingerprintInvalidated">Uwaga! Uwierzytelnienie biometryczne może zostać unieważnione przez Androida, np. po dodaniu nowego odcisku palca w ustawieniach urządzenia. Upewnij się, że zawsze wiesz, jak odblokować przy użyciu hasła głównego!</string>
<string-array name="ChangeLog_1_09e">
<item>Bug fix to crashes and unexpected log-outs</item>
<item>Naprawa błędów awarii i nieoczekiwanych wylogowań</item>
<item>Przełącz się na nową implementację SFTP, wspierając nowoczesne algorytmy klucza publicznego, takie jak rsa-sha2-256</item>
<item>Oznacz hasła jako wrażliwe podczas kopiowania do schowka (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>Dodano wsparcie dla przeglądania, usuwania i przywracania kopii zapasowych wpisów</item>

View File

@@ -303,6 +303,8 @@
<string name="NoDalVerification_summary">Desativa a checagem se domínio e pacote do app correspondem.</string>
<string name="InlineSuggestions_title">Integrar com o teclado</string>
<string name="InlineSuggestions_summary">Mostra sugestões de Autopreenchimento como opção na linha no teclado (se suportado pelo método de entrada)</string>
<string name="LogAutofillView_title">Registrar exibição de preenchimento automático</string>
<string name="LogAutofillView_summary">Escreva detalhes sobre a exibição de preenchimento automático para registro de depuração (se o registro de depuração estiver habilitado). Esses detalhes podem ser enviados ao desenvolvedor se o preenchimento automático não funcionar conforme o esperado.</string>
<string name="requires_android11">Requer Android 11 ou posterior</string>
<string name="kp2a_findUrl">Keepass2Android: Buscar senha</string>
<string name="excludeExpiredEntries">Excluir entradas expiradas</string>
@@ -690,6 +692,7 @@
<item>Correção de bugs em falhas e logouts inesperados</item>
<item>Mudar para nova implementação SFTP, suportando algoritmos de chave pública modernos como rsa-sha2-256</item>
<item>Marcar senhas como confidenciais ao copiar para a área de transferência (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>Adicionado suporte para visualização, remoção e restauração de backups de entrada</item>

View File

@@ -686,6 +686,12 @@
<string name="EntryChannel_desc">Notifikácia na zjednodušenie prístupu k práve vybranému záznamu.</string>
<string name="CloseDbAfterFailedAttempts">Zavrieť databázu po troch neúspešných odomykaniach s biometriou</string>
<string name="WarnFingerprintInvalidated">Varovanie! Biometrická autentifikácia môže byť zneplatnená systémom Android, nap. po pridaní nového odtlačku prsta do nastavení zariadenia. Vždy sa uistite, že viete ako odomknúť zariadenia primárnym heslom.</string>
<string-array name="ChangeLog_1_09e">
<item>Oprava chyby vedúcej k pádom a neočakávaným odhláseniam</item>
<item>Prepnutie na novú implementáciu SFTP, s podporou pre moderné algoritmy verejných kľúčov, ako je rsa-sha2-256</item>
<item>Označiť heslá ako citlivé údaje pri kopírovaní do schránky (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>Pridaná podpora prehliadania, odstraňovania a obnovovania záloh záznamu</item>
<item>Implementovaná podpora pre cloudové úložisko MEGA</item>

View File

@@ -686,6 +686,12 @@
<string name="EntryChannel_desc">Obvestilo o enostavnem dostopu do trenutno izbranega vnosa.</string>
<string name="CloseDbAfterFailedAttempts">Zaprite bazo podatkov po treh neuspelih poskusih biometričnega odklepanja.</string>
<string name="WarnFingerprintInvalidated">Opozorilo! Biometrično overjanje lahko Android razveljavi, npr. po dodajanju novega prstnega odtisa v nastavitvah naprave. Poskrbite, da boste vedno vedeli odkleniti z glavnim geslom!</string>
<string-array name="ChangeLog_1_09e">
<item>Popravek napak pri zrušitvah in nepričakovanih odjavah</item>
<item>Preklopite na novo izvedbo SFTP, ki podpira sodobne algoritme javnih ključev, kot je rsa-sha2-256</item>
<item>Označi gesla kot občutljiva pri kopiranju v odložišče (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>Dodana podpora za ogled, odstranjevanje in obnavljanje varnostnih kopij vnosov</item>
<item>Dodana podpora za shranjevanje v oblaku MEGA</item>

View File

@@ -686,6 +686,12 @@
<string name="EntryChannel_desc">Сповіщення для простого доступу до обраного запису.</string>
<string name="CloseDbAfterFailedAttempts">Закрити базу паролів після трьох невдалих спроб біометричного розблокування.</string>
<string name="WarnFingerprintInvalidated">Увага! Біометрична автентифікація може бути скасована Android, наприклад після додавання нового відбитку пальця у налаштуваннях вашого пристрою. Переконайтеся, що ви завжди знаєте, як розблокувати за допомогою головного пароля!</string>
<string-array name="ChangeLog_1_09e">
<item>Виправлено помилку зі збоями та несподіваними виходами</item>
<item>Перехід на нову реалізацію SFTP з підтримкою сучасних алгоритмів публічного ключа, як-от rsa-sha2-256</item>
<item>Позначати паролі як вразливі під час копіювання до буфера обміну (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>Додано підтримку перегляду, видалення та відновлення резервних копій записів</item>
<item>Додано підтримку хмарного сховища MEGA</item>

View File

@@ -2,7 +2,7 @@
<!--Generated by crowdin.net-->
<resources>
<string name="about_feedback">意見回饋:</string>
<string name="AboutText">Keepass2Android 是一個可以讀寫KeePass 2.x數據庫的Android 版本密碼管理軟體。</string>
<string name="AboutText">Keepass2Android 是 Android 的 KeePass 2.x 密碼資料庫管理軟體。</string>
<string name="CreditsText">界面基於由 Brian Pellin 所移植的 KeepassDroid。資料庫操作代碼基於由 Dominik Reichl 開發的 KeePass。Android 機器人圖示是以 Google 所創作及分享的作品,依據創用 CC- 姓名標示 3.0 授權條款而衍生及修改而成。</string>
<string name="CreditsTextSFTP">SFTP 支援使用 JCraft, inc. 所寫的 JSch 庫,而該庫使用 BSD 授權。</string>
<string name="CreditsIcons">錘子圖示、企鵝圖示、羽毛圖示和蘋果圖示均來自 Noun Project並依次分別由 John Caserta, Adriano Emerick, Jon Testa, Ava Rowell 所創作,而圖片圖示則來自 https://icons8.com/icon/5570/Picture 。</string>
@@ -24,14 +24,14 @@
<string name="app_timeout_summary">應用程式閒置指定時間後鎖定資料庫。</string>
<string name="kill_app_label">終止應用程式進程</string>
<string name="show_kill_app">關閉按鈕</string>
<string name="show_kill_app_summary">在密碼螢幕中顯示中止應用程式進程的按鈕 (供偏執的用戶使用)</string>
<string name="show_kill_app_summary">在密碼畫面中顯示中止應用程式進程的按鈕 (供偏執的用戶使用)</string>
<string name="application">應用程式</string>
<string name="application_settings">應用程式設定</string>
<string name="ShowGroupnameInSearchResult_title">在搜尋結果中顯示群組名稱</string>
<string name="ShowGroupnameInSearchResult_resume">在搜尋結果中的項目標題下顯示群組名稱,在不同項目具有相同的名稱時推薦使用。</string>
<string name="NavigationToGroupCompleted_message">顯示目前群組:%1$s</string>
<string name="AutofillDisabledQueriesPreference_title">用自動填入的對象</string>
<string name="AutofillDisabledQueriesPreference_summary">顯示已用自動填入的應用程式及網站清單</string>
<string name="AutofillDisabledQueriesPreference_title">用自動填入的對象</string>
<string name="AutofillDisabledQueriesPreference_summary">顯示已用自動填入的應用程式及網站清單</string>
<string name="OfferSaveCredentials_summary">啟用後若在可自動填入欄位手動輸入資料Android 將會詢問是否保存憑證。</string>
<string name="OfferSaveCredentials_title">提示儲存憑證</string>
<string name="ShowGroupInEntry_title">在項目畫面中顯示群組名稱</string>
@@ -52,7 +52,7 @@
<string name="import_keyfile_prefs">匯入金鑰檔案到內部資料夾</string>
<string name="export_keyfile_prefs">從內部資料夾匯出金鑰檔案</string>
<string name="keyboardswitch_prefs">切換鍵盤</string>
<string name="OnlyAvailableForLocalFiles">僅可用於本檔案。</string>
<string name="OnlyAvailableForLocalFiles">僅可用於本檔案。</string>
<string name="FileIsInInternalDirectory">檔存儲在內部目錄中。</string>
<string name="DatabaseFileMoved">資料庫檔案被複製到內部資料夾。按確定以從新位置打開。注意: 不要忘記定期將資料庫匯出到一個安全的存儲裝置!</string>
<string name="KeyfileMoved">金鑰檔案被複製到內部資料夾。從目前位置刪除之前請確保您留有一個安全的備份!</string>
@@ -62,8 +62,8 @@
<string name="brackets">括弧</string>
<string name="cancel">取消</string>
<string name="Ok">確定</string>
<string name="disable_sensor">停用感器</string>
<string name="enable_sensor">啟用感器</string>
<string name="disable_sensor">停用感</string>
<string name="enable_sensor">啟用感</string>
<string name="ClearClipboard">已清除剪貼簿。</string>
<string name="clipboard_timeout">剪貼簿超時</string>
<string name="clipboard_timeout_summary">複製使用者名稱或密碼後清除剪貼簿的時間</string>
@@ -71,7 +71,7 @@
<string name="copy_password">複製密碼到剪貼簿</string>
<string name="copy_totp">複製 TOTP 到剪貼簿</string>
<string name="available_through_keyboard">可通過 KP2A 鍵盤取得項目</string>
<string name="app_language_pref_title">應用語言</string>
<string name="app_language_pref_title">應用程式語言</string>
<string name="entry_is_available">可用</string>
<string name="not_possible_im_picker">無法打開選擇輸入法對話方塊。請手動啟動鍵盤。</string>
<string name="please_activate_keyboard">請在系統設定中啟用 Keepass2Android 鍵盤。</string>
@@ -90,8 +90,8 @@
<string name="fingerprint_success">生物識別驗證成功。</string>
<string name="fingerprint_os_error">生物識別解鎖需要 Android 6.0 或更高版本。</string>
<string name="fingerprint_hardware_error">未偵測到生物識別硬體。</string>
<string name="fingerprint_no_enrolled">還沒有在設備上設生物識別驗證,請先至系統設定設置</string>
<string name="disable_fingerprint_unlock">用生物識別解鎖</string>
<string name="fingerprint_no_enrolled">尚未在裝置上設生物識別驗證,請先至系統設定。</string>
<string name="disable_fingerprint_unlock">用生物識別解鎖</string>
<string name="enable_fingerprint_unlock">啟用完整生物識別解鎖</string>
<string name="enable_fingerprint_quickunlock">啟用 QuickUnlock 生物識別解鎖</string>
<string name="fingerprint_unlock_failed">生物識別解鎖失敗。系統無法驗證解密金鑰,你可能更改了安全設定或生物識別設定。 </string>
@@ -101,7 +101,7 @@
<string name="FingerprintInitFailed">無法初始化生物識別驗證。 </string>
<string name="FingerprintSetupFailed">資料加密失敗。若在系統設定中新增或刪除指紋時Keepass2Android 同時監聽您的指紋,就有可能會失敗。</string>
<string name="enable_fingerprint_unlock_Info">這將把主密碼儲存在此裝置上並透過Android 金鑰庫系統加密且將被生物識別驗證保護。此後你將可透過生物識別解鎖資料庫。</string>
<string name="enable_fingerprint_quickunlock_Info">將以生物識別證取代 QuickUnlock 代碼。這並不會在裝置上儲存與主密碼相關的任何資料。</string>
<string name="enable_fingerprint_quickunlock_Info">將以生物識別證取代 QuickUnlock 代碼。這並不會在裝置上儲存與主密碼相關的任何資料。</string>
<string name="enter_filename">輸入資料庫名:</string>
<string name="entry_accessed">存取時間</string>
<string name="entry_cancel">取消</string>
@@ -113,7 +113,7 @@
<string name="entry_expires">失效時間</string>
<string name="entry_group_name">群組名稱</string>
<string name="entry_keyfile">金鑰檔案(可選)</string>
<string name="keyfile_heading">金鑰檔</string>
<string name="keyfile_heading">金鑰檔</string>
<string name="entry_modified">修改時間</string>
<string name="entry_password">密碼</string>
<string name="entry_save">儲存</string>
@@ -131,19 +131,19 @@
<string name="error_failed_to_launch_link">運行鏈結失敗。</string>
<string name="error_filename_required">請求一個檔案名:</string>
<string name="error_file_not_create">無法建立檔案</string>
<string name="error_invalid_db">非法資料庫。</string>
<string name="error_invalid_path">非法路徑。</string>
<string name="error_invalid_db">無效資料庫。</string>
<string name="error_invalid_path">無效路徑。</string>
<string name="error_no_name">用戶名是必須的。</string>
<string name="error_nopass">密碼或者密鑰檔是必須的。</string>
<string name="error_nopass">密碼或鑰檔是必須的。</string>
<string name="error_pass_gen_type">至少必須選擇一個密碼生成類型</string>
<string name="error_pass_match">密碼不匹配。</string>
<string name="error_rounds_not_number">次數必須是數位。</string>
<string name="error_param_not_number">參數必須是數字。</string>
<string name="error_title_required">標題是必填項。</string>
<string name="error_wrong_length">在長度欄位輸入一個正整數</string>
<string name="FileNotFound">文件未找到。</string>
<string name="file_browser">流覽</string>
<string name="generate_password">密碼</string>
<string name="FileNotFound">檔案未找到。</string>
<string name="file_browser">案瀏覽器</string>
<string name="generate_password">生密碼</string>
<string name="group">群組</string>
<string name="hint_comment">備註</string>
<string name="hint_conf_pass">確認密碼</string>
@@ -158,7 +158,7 @@
<string name="hint_override_url">URL 覆蓋</string>
<string name="hint_tags">標籤1標籤2</string>
<string name="hint_username">使用者名稱</string>
<string name="InvalidPassword">不正確密碼或金鑰檔。</string>
<string name="InvalidPassword">無效密碼或金鑰檔</string>
<string name="invalid_algorithm">無效演算法。</string>
<string name="invalid_db_sig">資料庫格式無法識別。</string>
<string name="keyfile_does_not_exist">金鑰檔不存在。</string>
@@ -172,6 +172,10 @@
<string name="MaskedPassword">*****</string>
<string name="maskpass_title">遮罩密碼</string>
<string name="maskpass_summary">預設情況下隱藏密碼</string>
<string name="masktotp_title">遮罩 TOTP</string>
<string name="masktotp_summary">預設情況下隱藏 TOTP</string>
<string name="NoAutofillDisabling_title">無停用自動填入選項</string>
<string name="NoAutofillDisabling_summary">啟用後,應用程式將不顯示停用特定項目的自動填入選項。</string>
<string name="menu_about">關於</string>
<string name="menu_change_key">變更主金鑰</string>
<string name="menu_copy_pass">複製密碼</string>
@@ -218,7 +222,7 @@
<string name="remove_from_filelist">移除</string>
<string name="edit">編輯</string>
<string name="rijndael">Rijndael加密(AES)</string>
<string name="root">Root</string>
<string name="root">根目錄</string>
<string name="AutoReturnFromQuery_title">自動從查詢畫面返回</string>
<string name="AutoReturnFromQuery_summary">查找應用或網站的項目時:如資料庫裡只有一個相符項目,則從查詢畫面自動返回。</string>
<string name="KeyDerivFunc">金鑰派生函數</string>
@@ -242,13 +246,13 @@
<string name="sort_moddate">按修改日期排序</string>
<string name="sort_default">保持預設的順序</string>
<string name="special">特殊</string>
<string name="special_extended">擴展特殊</string>
<string name="special_extended">延伸特殊</string>
<string name="at_least_one_from_each_group">每群組至少一個</string>
<string name="exclude_lookalike">排除外觀相似的字符</string>
<string name="password_generation_profile">配置</string>
<string name="save_password_generation_profile_text">輸入要存的配置文件的名稱。輸入一個現有名稱覆蓋。 </string>
<string name="password_generation_profile">設定檔</string>
<string name="save_password_generation_profile_text">輸入要存的設定檔名稱。輸入現有名稱即可覆蓋。 </string>
<string name="hint_wordcount">密碼短語詞數</string>
<string name="hint_wordseparator">單詞分隔符</string>
<string name="hint_wordseparator">單詞分隔符</string>
<string-array name="PasswordGeneratorModes">
<item>密碼</item>
<item>密碼短語</item>
@@ -285,6 +289,8 @@
<string name="please_note">請注意</string>
<string name="contributors">貢獻者</string>
<string name="regular_expression">正規表示式</string>
<string name="AlwaysMergeOnConflict_title">衝突時一律合併</string>
<string name="AlwaysMergeOnConflict_summary">Keepass2Android 偵測到遠端檔案有修改時,一律合併本機和遠端變更</string>
<string name="TanExpiresOnUse_title">TAN 使用到期</string>
<string name="TanExpiresOnUse_summary">當使用它們時標記 TAN 項目過期</string>
<string name="ShowUsernameInList_title">在清單中顯示使用者名稱</string>
@@ -293,9 +299,9 @@
<string name="RememberRecentFiles_summary">記得最近打開的資料庫,並顯示在打開資料庫的螢幕中。</string>
<string name="NoDalVerification_title">無 DAL 驗證</string>
<string name="NoDalVerification_summary">不檢查域名是否符合應用程式包裹</string>
<string name="InlineSuggestions_title">集成到鍵盤</string>
<string name="InlineSuggestions_summary">自動填建議顯示為鍵盤中的內聯選項(如輸入法支持的話) </string>
<string name="requires_android11">需要Android 11或更高版本</string>
<string name="InlineSuggestions_title">整合到鍵盤</string>
<string name="InlineSuggestions_summary">在鍵盤中內嵌自動填建議選項 (如輸入法支) </string>
<string name="requires_android11">需要 Android 11 以上版本</string>
<string name="kp2a_findUrl">查找密碼</string>
<string name="excludeExpiredEntries">排除過期的項目</string>
<string name="search_options">選項</string>
@@ -323,6 +329,8 @@
<string name="QuickUnlockLength_summary">用作 QuickUnlock 密碼的最大字數。</string>
<string name="QuickUnlockHideLength_title">隱藏 QuickUnlock 代碼長度</string>
<string name="QuickUnlockHideLength_summary">若啟用QuickUnlock 代碼長度不會顯示在 QuickUnlock 畫面中。</string>
<string name="QuickUnlockKeyFromDatabase_title">以資料庫項目作為 QuickUnlock 金鑰</string>
<string name="QuickUnlockKeyFromDatabase_summary">目前資料庫內根目錄內有名為 QuickUnlock 的項目時,用其密碼為 QuickUnlock 代碼。</string>
<string name="QuickUnlock_fail">QuickUnlock 失敗:密碼錯誤!</string>
<string name="SaveAttachmentDialog_title">儲存附件</string>
<string name="SaveAttachmentDialog_text">請選擇保存該附件的位置。</string>
@@ -333,7 +341,7 @@
<string name="SaveAttachment_Failed">可不保存附件到 %1$s。</string>
<string name="AddUrlToEntryDialog_title">還記得搜尋的文字嗎?</string>
<string name="AddUrlToEntryDialog_text">你想將搜尋的文字「%1$s」儲存在所選的項目以便下次自動完成嗎?</string>
<string name="error_invalid_expiry_date">到期日期為不正確日期時間格式!</string>
<string name="error_invalid_expiry_date">失效時間或日期格式無效!</string>
<string name="error_string_key">每個字串都需要欄位名稱。</string>
<string name="error_string_duplicate_key">欄位名稱不能使用兩次 (%1$s)。</string>
<string name="field_name">欄位名稱</string>
@@ -341,12 +349,12 @@
<string name="protection">受保護的欄位</string>
<string name="add_binary">新增附件檔案</string>
<string name="add_extra_string">新增額外字串</string>
<string name="configure_totp">配置 TOTP</string>
<string name="totp_secret_key"></string>
<string name="totp_encoding_rfc6238">默認 RFC6238 令牌設定 </string>
<string name="configure_totp">設定 TOTP</string>
<string name="totp_secret_key">祕密金</string>
<string name="totp_encoding_rfc6238">預設 RFC6238 令牌設定</string>
<string name="totp_encoding_steam">Steam 令牌設定</string>
<string name="totp_encoding_custom">自訂令牌設定</string>
<string name="totp_time_step">時間步長</string>
<string name="totp_time_step">時間間隔</string>
<string name="totp_length">代碼長度</string>
<string name="totp_scan">掃描 QR 碼</string>
<string name="delete_extra_string">刪除額外字串</string>
@@ -378,11 +386,11 @@
<string name="LocalBackupOf">%1$s 的本機備份</string>
<string name="show_local_backups">顯示本機備份</string>
<string name="AcceptAllServerCertificates_title">SSL 憑證</string>
<string name="AcceptAllServerCertificates_summary">決定驗證憑證失敗時的行為。注意: 如果驗證失敗,您可以在設備上安裝憑證!</string>
<string name="AcceptAllServerCertificates_summary">決定驗證憑證失敗時的行為。注意: 如果驗證失敗,您可以在裝置上安裝憑證!</string>
<string name="ClearOfflineCache_title">清除快取?</string>
<string name="ClearOfflineCache_question">這會刪除所有資料庫快取副本。在無法存取原始資料庫檔案或離線尚未同步時,將會遺失所有變更!要繼續嗎?</string>
<string name="CheckForFileChangesOnSave_title">檢查修改</string>
<string name="CheckForFileChangesOnSave_summary">儲存前檢查該檔案是否有外部變更</string>
<string name="CheckForFileChangesOnSave_summary">儲存前檢查該檔案是否有外部修改</string>
<string name="CheckForDuplicateUuids_title">檢查有重複的 UUID</string>
<string name="CheckForDuplicateUuids_summary">由多個項目具有相同的 UUID 檢查資料庫檔案是否損壞。這可能會導致意外的行為。</string>
<string name="ShowCopyToClipboardNotification_title">剪貼簿通知</string>
@@ -396,7 +404,7 @@
<string name="OpenKp2aKeyboardAutomatically_title">切換鍵盤</string>
<string name="OpenKp2aKeyboardAutomatically_summary">從瀏覽器搜尋後,當項目可透過 KP2A 鍵盤取得時打開鍵盤選擇對話方塊 。</string>
<string name="kp2a_switch_rooted">自動切換鍵盤</string>
<string name="kp2a_switch_rooted_summary">項目開時自動切換為 KP2A 鍵盤。需設定好 KeyboardSwap切換鍵盤外掛程式或在已 Root 的設備中授予權限。</string>
<string name="kp2a_switch_rooted_summary">項目開時自動切換為 KP2A 鍵盤。需設定好 KeyboardSwap切換鍵盤外掛程式或在已 Root 的裝置中授予權限。</string>
<string name="get_keyboardswap">安裝 KeyboardSwap鍵盤切換外掛程式</string>
<string name="get_keyboardswap_summary">該外掛程式用於以 ADB 授予 KP2A 自動切換鍵盤的權限,而無需 Root。</string>
<string name="OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_title">搜尋後才自動切換</string>
@@ -411,8 +419,8 @@
<string name="DocumentAccessRevoked">Keepass2Android 無法存取這檔案。檔案或其存取權可能被移除了,請重新開啟它,如「變更資料庫」。</string>
<string name="PreloadDatabaseEnabled_title">預載入資料庫檔案</string>
<string name="PreloadDatabaseEnabled_summary">啟用背景載入、或在輸入密碼時下載資料庫檔案。</string>
<string name="SyncAfterQuickUnlock_title">QuickUnlock後同步</string>
<string name="SyncAfterQuickUnlock_summary">使用 QuickUnlock 解鎖後將本地數據庫與遠程文件同步</string>
<string name="SyncAfterQuickUnlock_title">QuickUnlock 後同步</string>
<string name="SyncAfterQuickUnlock_summary">QuickUnlock 解鎖後從遠端檔案同步資料庫</string>
<string name="AskOverwriteBinary">是否以相同名稱覆蓋現有檔案?</string>
<string name="AskOverwriteBinary_title">覆蓋現有檔案?</string>
<string name="AskOverwriteBinary_yes">覆寫</string>
@@ -474,7 +482,7 @@
<string name="otp_aux_file">一次性密碼輔助檔</string>
<string name="ErrorOcurred">發生錯誤:</string>
<string name="DuplicateUuidsError">資料庫已損壞:發現重複 ID。(你或曾用 Minikeepass 編輯它?) 請用電腦版 Keepass 2 重新導入到新的資料庫與並選擇「建立新的 ID」。</string>
<string name="DuplicateUuidsErrorAdditional">您可以在「設定/應用程式設定/檔案處理/檢查重複 UUID」中用此錯誤訊息。請注意,您可能還會遇到意外狀況,仍建議對資料庫進行修</string>
<string name="DuplicateUuidsErrorAdditional">您可以在「設定/應用程式設定/檔案處理/檢查重複 UUID」中用此錯誤訊息。請注意,您可能還會遇到意外狀況,仍建議對資料庫進行修</string>
<string name="synchronize_database_menu">同步資料庫...</string>
<string name="CannotMoveGroupHere">無法將群組移動到此群組。</string>
<string name="donate_question">今天,它是慕尼克啤酒節!如果你喜歡 Keepass2Android: 今天不是個好的日子買一杯啤酒給我嗎?</string>
@@ -495,7 +503,7 @@
<string name="sftp_auth_mode">身份驗證模式</string>
<string name="send_public_key">發送公鑰...</string>
<string name="enter_ftp_login_title">輸入 FTP 登入資訊:</string>
<string name="enter_mega_login_title">輸入您的 MEGA 帳登入資料</string>
<string name="enter_mega_login_title">輸入 MEGA 帳登入資料</string>
<string name="select_storage_type">選擇存儲類型:</string>
<string name="filestoragename_file">本機檔案</string>
<string name="filestoragename_androidget">從第三方應用程式取得</string>
@@ -509,7 +517,8 @@
<string name="filestoragename_dropboxKP2A">Dropbox (KP2A 資料夾)</string>
<string name="filestoragehelp_dropboxKP2A">使用此選項只請求「應用程式/Keepass2Android」 路徑的存取權,而非授予 KP2A 存取 Dropbox 全部空間的權限。這選項更適合新建資料庫。如果要存取現有資料庫,按一下此選項建立該資料夾,然後 (從您的個人電腦) 將資料庫檔案放在資料夾內,再點擊此選項打開檔案。</string>
<string name="filestoragename_gdrive">Google 雲端硬碟</string>
<string name="filestoragename_gdriveKP2A">Google 雲端硬碟 (KP2A檔案)</string>
<string name="filestoragename_gdriveKP2A">Google 雲端硬碟 (KP2A 檔案)</string>
<string name="filestoragehelp_gdriveKP2A">如不想讓 KP2A 存取整個 Google 雲端硬碟,請選此項。請注意此選項會使程式無法存取現有檔案,需要先建立資料庫檔案。請在建立資料庫或匯出已開啟的資料庫時,選擇此選項。</string>
<string name="filestoragename_pcloud">pCloud</string>
<string name="filestoragehelp_pcloud">這個儲存類型只會請求存取 pCloud「Applications/Keepass2Android」資料夾。要使用你 pCloud 賬戶裏現有的資料庫,請確保其檔案置於該 pCloud 資料夾。</string>
<string name="filestoragename_onedrive">OneDrive</string>
@@ -519,17 +528,18 @@
<string name="filestoragename_onedrive2_appfolder">Keepass2Android 應用程式資料夾</string>
<string name="filestoragename_sftp">SFTP (SSH 檔案傳輸)</string>
<string name="filestoragename_mega">MEGA</string>
<string name="filestoragehelp_mega">請注意Keepass2Android 必須下載 Mega 帳戶中的所有檔案清單才能運作。因此,存取擁有大量檔案的帳戶可能很慢。</string>
<string name="filestoragename_content">系統檔選擇器</string>
<string name="filestorage_setup_title">檔案存取權限初始化</string>
<string name="database_location">資料庫位置</string>
<string name="help_database_location">您可以在 Android 設備或在雲端上 (僅非離線版本可用) 儲存您的資料庫。即使裝置離線Keepass2Android 仍可使用資料庫。資料庫將安全地以 256 位 AES 加密,除了你任何人都無法存取您的密碼。我們建議使用 Dropbox: 你能夠在你的所有裝置上存取它,甚至透過它存取以前的備份版本。</string>
<string name="help_database_location">您可以在 Android 裝置或雲端上 (僅非離線版本可用) 儲存您的資料庫。即使裝置離線Keepass2Android 仍可使用資料庫。資料庫將安全地以 256 位 AES 加密,除了你任何人都無法存取您的密碼。我們建議使用 Dropbox: 你能夠在你的所有裝置上存取它,甚至存取以前的備份版本。</string>
<string name="hint_database_location">選擇位置來存儲資料庫:</string>
<string name="button_change_location">變更位置</string>
<string name="help_quickunlock">若啟用Keepass2Android 在鎖定資料庫時仍於背景執行。稍後輸入主密碼的一部份即可解鎖。</string>
<string name="master_password">主密碼</string>
<string name="help_master_password">您的資料庫將以您在此處輸入的密碼進行加密。為了保證資料庫的安全,選擇強式密碼!提示: 自創一兩英文句子,使用句中單詞首字母作為密碼。包括標點符號。</string>
<string name="hint_master_password">選擇一個主密碼來保護您的資料庫:</string>
<string name="key_file">金鑰檔</string>
<string name="key_file">金鑰檔</string>
<string name="help_key_file">金鑰檔案就是檔案形式的密碼。金鑰檔案比主密碼更複雜,因而更安全,但也難維持隱密。請不要將資料庫與金鑰檔案同時儲存在雲端,不然金鑰檔案則變得毫無用處!重要提示:建立資料庫後不要更改金鑰檔案的內容!</string>
<string name="hint_key_file">選擇是否除主密碼外,同時使用金鑰檔案:</string>
<string name="use_key_file">使用金鑰檔</string>
@@ -562,7 +572,7 @@
<string name="TrayTotp_prefs">TrayTotp</string>
<string name="DebugLog_prefs_prefs">偵錯用運行日誌</string>
<string name="DebugLog_title">啟用運行日誌</string>
<string name="DebugLog_summary">記錄應用程式輸出至本日誌檔案</string>
<string name="DebugLog_summary">記錄應用程式輸出至本日誌檔案</string>
<string name="DebugLog_send">發送偵錯日誌…</string>
<string name="loading">載入中…</string>
<string name="plugins">外掛程式</string>
@@ -570,7 +580,7 @@
<string name="plugin_description">描述 (未驗證):</string>
<string name="plugin_author">作者 (未驗證):</string>
<string name="plugin_enabled">啟用</string>
<string name="plugin_disabled"></string>
<string name="plugin_disabled"></string>
<string name="plugin_web">查找線上外掛程式</string>
<string name="plugin_scopes">範圍</string>
<string name="not_enabled">未啟用</string>
@@ -586,8 +596,8 @@
<string name="SCOPE_QUERY_CREDENTIALS_title">查詢憑證</string>
<string name="SCOPE_QUERY_CREDENTIALS_explanation">外掛程式將獲准查詢指定網站或應用程式的憑證。</string>
<string name="get_regular_version">獲取更多的存儲類型</string>
<string name="CertificateWarning">警告: 無法驗證伺服器憑證: %1$s。請為設備安裝相應根憑證,或參閱設定!</string>
<string name="CertificateFailure">錯誤: 無法驗證伺服器憑證!請為設備安裝相應根憑證,或參閱設定!</string>
<string name="CertificateWarning">警告: 無法驗證伺服器憑證: %1$s。請為裝置安裝相應根憑證,或參閱設定!</string>
<string name="CertificateFailure">錯誤: 無法驗證伺服器憑證!請為裝置安裝相應根憑證,或參閱設定!</string>
<string name="export_fileformats_title">選擇檔案格式</string>
<string name="killed_by_os">抱歉!Keepass2Android 進程被 Android OS 殺了出於安全原因Keepass2Android 不堅持使用你裝置上所選的憑証,所以你需要重新打開您的資料庫。注: 這應該很少發生。如果是這樣,請聯絡我 crocoapps@gmail.com。</string>
<string name="FileIsTemporarilyAvailable">該檔案暫時僅於 Keepass2Android 可用。</string>
@@ -624,7 +634,7 @@
<string name="TemplateTitle_Membership">會員</string>
<string name="ChangeLog_title">變更紀錄</string>
<string name="AskAddTemplatesTitle">新增範本?</string>
<string name="AskAddTemplatesMessage">Keepass2Android 附有一些 E-Mail 帳、無線網路密碼、安全記事等項目範本。要把這些加到資料庫中嗎?您稍後仍可在資料庫設定中加入這些範本。</string>
<string name="AskAddTemplatesMessage">Keepass2Android 附有一些 E-Mail 帳、無線網路密碼、安全記事等項目範本。要把這些加到資料庫中嗎?您稍後仍可在資料庫設定中加入這些範本。</string>
<string name="AddTemplates_pref">新增範本到資料庫</string>
<string name="PreviewWarning">請注意!你正使用預覽版本,使用時可能會出現問題!如果你遇到任何問題,煩請以 Google+ 測試者群組或電子郵件告知。</string>
<string name="Continue">繼續</string>
@@ -637,7 +647,7 @@
&#8226; 確保已選擇正確的資料庫檔案。
</string>
<string name="HintLocalBackupInvalidCompositeKey"> \n
&#8226; 提示:如果你認爲資料庫檔案可能會受損,或你有可能忘記修改後的主金鑰,你可以嘗試點擊「%1$s」開啓最後一個成功開的版本並選擇本機備份。</string>
&#8226; 提示:如果你認爲資料庫檔案可能會受損,或你有可能忘記修改後的主金鑰,你可以嘗試點擊「%1$s」開啓最後一個成功開的版本並選擇本機備份。</string>
<string name="HintLocalBackupOtherError"> \n
&#8226; 提示Keepass2Android已經把最後一個開啓成功的檔案版本存儲在內部儲存裝置中。你可以點擊「%1$s」開啓它並選擇本機備份。
</string>
@@ -654,20 +664,20 @@
<string name="configure_child_dbs">配置子資料庫…</string>
<string name="child_dbs_title">子資料庫</string>
<string name="unspecified">未指定</string>
<string name="child_db_explanation">在父資料庫打開時,子資料庫可以自動打開。因此,子資料庫的主密碼與檔案位置會儲存在父資料庫中。這功能可用於跟其他人分享密碼,與 PC 的 KeeAutoExec 相容。</string>
<string name="child_db_enabled_on_this_device">已在此設備上啟用</string>
<string name="child_db_enable_on_this_device">在此設備上啟用</string>
<string name="child_db_disable_on_this_device">在此設備上禁</string>
<string name="child_db_enable_a_copy_for_this_device">為此設備建立副本</string>
<string name="unconfigured_child_dbs">你的資料庫包含在「AutoOpen」群組裡的新子資料庫。請指明是否在此設備上使用這些子資料庫。</string>
<string name="child_db_explanation">在父資料庫打開時,子資料庫可以自動打開。因此,子資料庫的主密碼與檔案位置會儲存在父資料庫中。這功能可用於跟其他人分享密碼,而實作與 PC 的 KeeAutoExec 相容。</string>
<string name="child_db_enabled_on_this_device">已在此裝置上啟用</string>
<string name="child_db_enable_on_this_device">在此裝置上啟用</string>
<string name="child_db_disable_on_this_device">在此裝置上停</string>
<string name="child_db_enable_a_copy_for_this_device">為此裝置建立副本</string>
<string name="unconfigured_child_dbs">你的資料庫包含在「AutoOpen」群組裡的新子資料庫。請指明是否在此裝置上使用這些子資料庫。</string>
<string name="add_child_db">新增子資料庫...</string>
<string name="EnableCopyForThisDevice_Info">這將建立並啟用該子資料庫設定的副本,使得設定可針對此設備特別調整。</string>
<string name="EnableCopyForThisDevice_Info">這將建立並啟用該子資料庫設定的副本,使得設定可針對此裝置特別調整。</string>
<string name="Visible_title">可見</string>
<string name="child_db_Enabled_title">自動開啟</string>
<string name="database_file_heading">資料庫檔案</string>
<string name="if_device_text">在 %1$s 上啟用</string>
<string name="restore_history">還原這個版本</string>
<string name="remove_history">移除這個版本</string>
<string name="restore_history">還原版本</string>
<string name="remove_history">移除版本</string>
<string name="DbUnlockedChannel_name">已解鎖資料庫</string>
<string name="DbUnlockedChannel_desc">已解鎖資料庫的通知</string>
<string name="DbQuicklockedChannel_name">QuickUnlock</string>
@@ -675,25 +685,39 @@
<string name="EntryChannel_name">項目通知</string>
<string name="EntryChannel_desc">簡化所選項目存取流程的通知</string>
<string name="CloseDbAfterFailedAttempts">三次生物識別解鎖失敗後關閉資料庫</string>
<string name="WarnFingerprintInvalidated">警告!生物識別驗證可能會被Android系統作廢例如當在你的設備設定中新增一個新的指紋後)。請確保你總是知道如何用你的主密碼解鎖 </string>
<string name="WarnFingerprintInvalidated">警告Android 可使生物識別驗證失效(例如在裝置設定中新增指紋後)。請確保您一律記得解鎖用的主密碼! </string>
<string-array name="ChangeLog_1_09e">
<item>修正當機和意外登出的錯誤</item>
<item>切換成新 SFTP 實作,支援現代公鑰演算法,如 rsa-sha2-256</item>
<item>複製密碼到剪貼簿時設為機密 (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>支援查看、移除和還原項目備份</item>
<item>支援 MEGA 雲端存儲</item>
<item>支援限制訪問範圍的 Google 雲端硬碟</item>
</string-array>
<string-array name="ChangeLog_1_09c">
<item>重新實作 Google 雲端硬碟的驗證,重啟其支援</item>
</string-array>
<string-array name="ChangeLog_1_09b">
<item>復了Firefox自動填入提示消息的問題</item>
<item>自動填入建議提示集成到鍵盤 (需要安卓11或更高版本)</item>
<item>允許在設中修改應用程式語言</item>
<item>新增QuickUnlock后同步數據庫的選項</item>
<item>漏洞修復:保存到 Dropbox 時,不會將文件名改成小寫</item>
<item>Firefox 中消失的自動填入提示</item>
<item>整合自動填入建議提示到鍵盤 (需要 Android 11+)</item>
<item>允許在設中修改應用程式語言</item>
<item>新增 QuickUnlock 後同步資料庫的選項</item>
<item>修正錯誤:不再將 Dropbox 檔案儲存為小寫名稱</item>
</string-array>
<string-array name="ChangeLog_1_09a">
<item>新增對 KeePass 2.48 時引入的 KDBX 4.1 檔案格式的支持</item>
<item>新增設項目TOTP的對話方塊</item>
<item>改進密碼生成器:添加了 對 passphrase 的支持、更多選項、配置文件和密碼強度估算</item>
<item>自動填入 (解決彈出式視窗未在Chrome中顯示的問題并對子域提供更好的支持)</item>
<item>Improvements to OneDrive implementation: no more size limit, no more superfluous authentication requests</item>
<item>Added option to select light/dark design from system settings including night plans, requires Android 10+</item>
<item>Update Dropbox implementation to support new authentication method.</item>
<item>Newly setup fingerprint unlock will be invalidated after adding a fingerprint in system settings for increased security.</item>
<item>Allow to open files through system file picker, ignoring the read-only flag</item>
<item>Allow to start moving entries from the entry view menu</item>
<item>支援 KeePass 2.48 新增的 KDBX 4.1 檔案格式</item>
<item>新增設項目 TOTP 的對話方塊</item>
<item>改進密碼產生器:支援密碼短語、更多選項、設定檔和密碼強度估算</item>
<item>自動填入 (修正彈出式視窗未在 Chrome 中顯示的問題,並改善子網域支援)</item>
<item>改進 OneDrive 實作:無大小限制、無非必要的驗證請求</item>
<item>新增選擇系統設定中的淺色/深色主題,自動開啟也同樣適用,需要 Android 10+</item>
<item>更新 Dropbox 實作,支援新驗證方法</item>
<item>系統設定中新增指紋後,新設定的指紋解鎖會失效,更為安全。</item>
<item>允許以系統檔案選擇器開啟檔案,忽略唯讀標記</item>
<item>允許從項目畫面的選單移動項目</item>
</string-array>
<string-array name="ChangeLog_1_08d">
<item>支援 Keepass 2.47 新增的金鑰檔案格式</item>
@@ -712,13 +736,13 @@
<item>修正缺失的翻譯</item>
</string-array>
<string-array name="ChangeLog_1_08b">
<item>因 Okhttp 的 HTTP/2 實問題,而強制使用 HTTP/1.1 </item>
<item>因 Okhttp 的 HTTP/2 實問題,而強制使用 HTTP/1.1 </item>
<item>改進 Android 9+ 上的鍵盤對話方塊</item>
<item>變更檔案關聯而避免開啟無關檔案</item>
<item>確保密碼不被眼睛圖示掩蓋</item>
<item>變更自動填入行為,填入網域憑證到未知程式時警告使用者</item>
<item>更新 FTP 庫</item>
<item>修正應用程式的潛在崩潰</item>
<item>修正應用程式的潛在當機錯誤</item>
<item>更多補丁</item>
</string-array>
<string-array name="ChangeLog_1_08">
@@ -728,18 +752,18 @@
<item>錯誤修正</item>
</string-array>
<string name="ChangeLog_1_07b"> 版本 1.07b\n
* 以原生方式實現 Argon2 演算改進性能(感謝 Chih-Hsuan Yen\n
* 以原生 Argon2 實作演算改進性能(感謝 Chih-Hsuan Yen\n
* 可停用指紋,點擊指紋圖示即可,以避免屏下指紋識別產生的問題(感謝 marcoDallas\n
* 在開關密碼顯示時,還原滑鼠指標位置(感謝 DDoSolitary\n
* 改進 pCloud 雲端實現(再次感謝 gilbsgilbs\n
* 新增對數個瀏覽器的自動填入支\n
* 全新的 OneDrive 雲端實:涵蓋商務用 OneDrive已共用檔案可選訪問範圍多個賬戶還修正了離線訪問的問題\n
* 錯誤修正
* 新增對數個瀏覽器的自動填入支\n
* 全新的 OneDrive 雲端實:涵蓋商務用 OneDrive已共用檔案可選訪問範圍多個賬戶還修正了離線訪問的問題\n
* 修正錯誤
</string>
<string name="ChangeLog_1_07">版本 1.07\n
* 修正三星 Android 9 上的崩潰\n
* 修正三星 Android 9 上的當機錯誤\n
* 允許開啟一個以上的資料庫,與 KeeAutoExec 相容\n
* SFTP: 允許公開金鑰證,檢查主機金鑰是否改變\n
* SFTP: 允許公開金鑰證,檢查主機金鑰是否改變\n
* 導入 pCloud 支援 - 感謝 gilbsgilbs\n
* 更明確的 Nextcloud 支援\n
* 改進項目附件的儲存與更新\n
@@ -748,29 +772,29 @@
* 改善自動填入 (現在可以用於 Firefox允許減少彈窗)\n
* 錯誤修正\n </string>
<string name="ChangeLog_1_06"> 版本 1.06\n
* 以 ykDroid 取代 YubiChallenge 作爲處理 Yubikey 質詢-回應的應用。\n
* 以 ykDroid 取代 YubiChallenge 作爲處理 Yubikey 挑戰 - 回應的程式。\n
* 支援 KeepassXC 式的挑戰 - 回應。備註:資料庫格式必須爲 KDBX4\n
* 拒絕載入 Google 雲端硬碟中已刪除的文件\n
* 更換 FTPS 的 TLS 實,並添加 JSch 在與支援 gssapi-with-mic 的伺服器協作時出錯的解決辦法\n
* 錯誤修正\n
</string>
* 拒絕載入 Google 雲端硬碟中已刪除的檔案\n
* 更換 FTPS 的 TLS 實,並添加 JSch 在與支援 gssapi-with-mic 的伺服器協作時出錯的解決辦法\n
* 修正錯誤\n
</string>
<string name="ChangeLog_1_05"> 版本 1.05\n
* 使用 Android 8 的通知渠道,允許使用系統設定修改通知\n
* 在通知中顯示項目圖示\n
* 使用 Android 8 的自適應圖示,在 Android 7 中使用圓形圖示\n
* 允許解鎖後啟動搜尋 (見設定)\n
* 改變檔案透過儲存空間存取架構寫入的方式,修正透過系統檔案選擇時更新 Google 雲端硬碟的檔案的問題\n
* 加入一些註解文字以避免誤會\n
* 加入註解文字以避免誤會\n
* 為成功開啟的資料庫建立本機備份以減少數據丟失的風險\n
* 更新 JSch 以支援更多更新的 SSH 加密算法\n
* 允許修改連接設定,如 WebDav 的密碼變更\n
* 加入對 Yubikey Neo 靜態密碼模式的支援\n
* 允許關閉自動填入建議\n
* 允許停用自動填入建議\n
* 修正對 logcat 的資料泄漏\n
* 錯誤修正\n
* 修正錯誤\n
</string>
<string name="ChangeLog_1_04b"> 版本 1.04b\n
* 避免華為裝置啟用自動填入時崩潰。\n
* 避免華為裝置啟用自動填入時當機。\n
</string>
<string name="ChangeLog_1_04"> 版本 1.04\n
* 新增 Android 8.0 或以上的自動填入服務。\n
@@ -786,39 +810,39 @@
<string name="ChangeLog_1_02"> Version 1.02\n
* 數個安全改進。萬分感謝來自 jean-baptiste.cayrou@thalesgroup.com和vincent.fargues@thalesgroup.com 二位的安全報告與合作!\n
* 支援 KeyboardSwapPlugin (見「項目密碼存取」設定): 允許在非 Root 裝置上自動切換輸入法。感謝來自 XDA-Developers 的 Mishaal Rahman 使其可行。\n
* 修無障礙輔助服務與最近數版 Chrome 的相容性\n
* 修不必的指紋數據清除\n
* 修復崩潰\n
* 修無障礙輔助服務與最近數版 Chrome 的相容性\n
* 修不必的指紋資料清除\n
* 修正當機\n
* 升級 Dropbox SDK 確定未來的相容性\n
* 透過 Xamarin Insights 移除錯誤回報\n
* 移除用於錯誤回報的 Xamarin Insights \n
* 升級構建工具\n
</string>
<string name="ChangeLog_1_01g"> 版本 1.01-g\n
* 修嘗試離線操作時的崩潰\n
* 修不正確的 FTP(S) 身份驗證資訊編碼\n
* 修復在舊 Android 版本使用 OneDrive 發生的崩潰\n
* 在項目畫面以本時間顯示時間\n
* 修嘗試離線操作時的當機\n
* 修不正確的 FTP(S) 身份驗證資訊編碼\n
* 修正舊版本 Android 使用 OneDrive 時當機\n
* 在項目畫面以本時間顯示時間\n
</string>
<string name="ChangeLog_1_01d"> 版本 1.01-d\n
* 修 OneDrive 檔案清單\n
* 修 OneDrive 檔案清單\n
* 在主機名稱驗證失敗時,允許忽略憑證錯誤 (不要用於生產環境)\n
* 修有時候輸入正確 QuickUnlock 代碼而解鎖失敗的問題\n
* 修有時候輸入正確 QuickUnlock 代碼而解鎖失敗的問題\n
</string>
<string name="ChangeLog_0_9_8c"> 版本0.9.8c\n
* 修正 Microsoft Live SDK (用於存取 OneDrive 檔案) 的 SSL 漏洞。\n
* 修正之前的版本內建兩個輸入法,其一崩潰的問題。\n
* 修正之前的版本內建兩個輸入法,其一當機的錯誤。\n
</string>
<string name="ChangeLog_1_01">版本1.01\n
* 支持 KDBX-4 格式(相容於 KeePass 2.35),包括 Argon2 密鑰推導和 ChaCha20 加密。\n
* 重新構建 WebDav 文件存儲,現可進行瀏覽並支持現代加密(例如 SSL。\n
* 重新構建 FTP 文件存儲,現可進行瀏覽並支持加密的 FTPS 。\n
* 重新構建 WebDav 檔案存儲,現可進行瀏覽並支持現代加密(例如 SSL。\n
* 重新構建 FTP 檔案存儲,現可進行瀏覽並支持加密的 FTPS 。\n
* 更新 OneDrive SDK此前使用的 Live SDK 不再更新)。\n
* 更新 Dropbox SDK 第2版此前使用的第1版 SDK 已棄用)。\n
* 支持 OwnCloud 。\n
* 在打開本地文件之前提示儲權限。 </string>
* 在打開本機檔案之前提示儲權限。 </string>
<string name="ChangeLog_1_0_0e">版本1.0.0e\n
* 修舊版三星設備在 Android 6 中出現的指紋解鎖異常。\n
* 原生支持 x86 設備。\n
* 修舊版三星裝置在 Android 6 中出現的指紋解鎖異常。\n
* 原生支持 x86 裝置。\n
* 允許在指紋掃描期間隱藏虛擬鍵盤。\n
* 構建系統更新。 </string>
<string name="ChangeLog_1_0_0"> 版本 1.0.0\n
@@ -830,7 +854,7 @@
* 欄位名稱的自動完成模式\n
* 允許從最近文件清單中移除項目\n
* 在 Android 6.0 運行時請求權限\n
* 錯誤修復 (在內建鍵盤, 選擇圖示時)\n
* 修正錯誤 (在內建鍵盤, 選擇圖示時)\n
* 包含發送錯誤報告的選項\n
* 在數處新增幫助訊息\n
</string>
@@ -842,13 +866,13 @@
* 檢查額外欄位的重複項目以免丟失數據\n
</string>
<string name="ChangeLog_0_9_9c"> 版本 0.9.9c \n
* 色主題回歸 \n
* 色主題回歸 \n
* 您可安裝其他圖示包Play 商店可下載到經典 Windows 風格的圖示) \n
* 直接移除項目而不放入回收桶時添加確認提示 \n
* 錯誤修正:因一次性密碼檔編碼問題所致的錯誤顯示、某些場景下顯示錯誤的圖示\n
* 修正錯誤:因一次性密碼檔編碼問題所致的錯誤顯示、某些場景下顯示錯誤的圖示\n
</string>
<string name="ChangeLog_0_9_8b">版本 0.9.8b \n
* 錯誤修正:某些資料庫存取失敗、匯出至本機運作異常、個別偏好設定導致無回應之情形。\n</string>
* 修正錯誤:某些資料庫存取失敗、匯出至本機運作異常、選擇個別偏好設定導致當機之情形。\n</string>
<string name="ChangeLog_0_9_8">版本 0.9.8\n
* 支援儲存空間存取架構 (允許在離線版 KP2A 中讀寫 SD 卡與 Google 雲端硬碟)\n
* 嘗試偵測用戶輸入 WebDAV 的錯誤網址 (定位於資料夾而非檔案)\n
@@ -857,43 +881,43 @@
* 修正錯誤: 現可記住一次性 (OTP) 密碼</string>
<string name="ChangeLog_0_9_7b">版本 0.9.7b \n
* 更新翻譯 \n
* 錯誤修正0.9.7 版本中缺少密碼字體、按名稱排序沒有排序群組 \n</string>
* 修正錯誤0.9.7 版本中缺少密碼字體、群組沒有按名稱排序 \n</string>
<string name="ChangeLog_0_9_7">版本 0.9.7 \n
* 支援讀寫 Keepass 1 (kdb) 資料庫(測試版)\n
* 更完美的切回原先的鍵盤(無需 ROOT\n
* 支援 KeeChallenge 外掛程式與可變長度的金鑰檔 \n
* 阻止在快速解鎖和鍵入密碼面截取幕 \n
* 阻止在 QuickUnlock 和鍵入密碼面截取幕 \n
* 修改按日期排序的反向順序為降序 \n
* 錯誤修正:註釋現可正確更新、密碼現可在所有設備上隱藏顯示(但願如此)、修正同一條目可添加兩次的問題、修正即便在修資料庫後仍然多次彈出 UUID 警示的問題 \n</string>
* 修正錯誤:註釋現可正確更新、密碼現可在所有裝置上隱藏顯示(但願如此)、修正同一條目可新增兩次的問題、修正即便在修資料庫後仍然多次彈出 UUID 警示的問題 \n</string>
<string name="ChangeLog_0_9_6">版本 0.9.6 \n
* 允許將金鑰或本機資料庫檔案導入到應用程式內部資料夾(參見設定)\n
* 允許將金鑰或本機資料庫檔案導入到應用程式內部資料夾(參見設定)\n
* 支援多種排序方法 \n
* 改進了自動鍵盤切換的偏好 \n
* 更新了應用徽標和通知欄設計,由 Pignataro (http://www.spstudio.at) 提供 \n
* 密碼產生器記住上次的設定 \n
* Android 5 幕鎖定時的通知消息設定可見性 \n
* 設定 Android 5 幕鎖定時的通知可見性 \n
* 離開應用程式但未點擊「確定」時清除主密碼欄位 \n
* 修正部分設備的鍵盤設定中缺少輸入語言的問題 \n
* 修正已 ROOT 設備上自動鍵盤切換的問題 \n
* 修正部分裝置的鍵盤設定中缺少輸入語言的問題 \n
* 修正已 ROOT 裝置上自動鍵盤切換的問題 \n
* 添加資料庫檢查功能(重複的 UUID\n
* 在檢測到更改時自動重新載入資料庫,解決了顯示主密碼的安全隱患 \n
* 改進了波蘭語小鍵盤布局,修正鍵盤設定佈景(感謝 Wiktor Ławski \n</string>
* 改進了波蘭語小鍵盤布局,修正鍵盤設定主題(感謝 Wiktor Ławski \n</string>
<string name="ChangeLog_0_9_5"><b>版本 0.9.5</b>\n
* 修正檔案查看(特別是 Android 4.4)中的問題 \n
* 修正運行 Android Lollipop 的 Nexus 5 載入 .kbd (keepass 1) 檔的問題 \n
* 增設定以在總覽清單中隱藏快照或應用程式 \n
* 修正 Google Drive 檔案存取的問題(常規版)\n
* 增設定以在總覽清單中隱藏應用程式,及防止螢幕截圖 \n
* 修正 Google 雲端硬碟檔案存取的問題(常規版)\n
* 允許金鑰檔按指定的檔案類型存取(常規版)\n
* 更新 DropBox SDK 與安全性修補程式(常規版)\n
* 更新構建工具 --&gt; APK 程式檔體積增加 :-( \n
我保證還會有更多的更新,它們將在下個版本中到來。-- 抱歉,我想先盡快推送這些熱修程式。</string>
我保證還會有更多的更新,它們將在下個版本中到來。-- 抱歉,我想先盡快推送這些熱修程式。</string>
<string name="ChangeLog_0_9_4"><b>版本 0.9.4</b>\n
* 支援外掛程式:參閱設定以獲取外掛程式\n
* 發表 QR 碼外掛 (掃描密碼、以 QR 碼顯示密碼、將條目傳送到其他 KP2A 設備)\n
* 發表 QR 碼外掛 (掃描密碼、以 QR 碼顯示密碼、將條目傳送到其他 KP2A 裝置)\n
* 發表 InputStick 外掛 (透過藍牙將數位憑證傳送到您的電腦 - 需搭配 InputStick USB 外設)\n
* 第三方應用程式現可輕鬆查詢 KP2A 的憑證。你是開發者嗎?若合適請為您的應用增設這一支援!\n
* 第三方應用程式現可輕鬆查詢 KP2A 的憑證。你是開發者嗎?若合適請為您的程式增設這一支援!\n
* 支援 TOTP 兩步驟驗證 (與 KeeOTP 和 TrayTotp 外掛相容)\n
* 打開資料庫時,應用程不再被 Android 休眠\n
* 打開資料庫時,應用程不再被 Android 休眠\n
* 使用後退按鈕離開程式時,數據庫不再被鎖定 (見設定)\n
* 搜尋結果中顯示群組名稱 (*)\n
* 搜尋結果中添加選單,包含「移動到上層目錄」(*)\n
@@ -902,17 +926,17 @@
* 支援 KeeChallenge 的 Yubikey NEO 外掛。感謝 Ben Rush 實現這一功能!\n
* 改進使用者介面\n
* 修正 Google 雲端硬碟介面中的錯誤\n
* 加入了用「捐贈」選項的選項\n
* 加入了用「捐贈」選項的選項\n
* 在 Android 4.2+ 的設備上改為預設隱藏 QuickUnlock 圖示\n </string>
<string name="ChangeLog_0_9_3_r5"><b>版本 0.9.3 r5</b>\n
* 合併來自 Xamarin 的修補程式Keepass2Android 現在與 Android 4.4.2 上的 ART 虛擬機相容。\n
* 修正:同步過程中的錯誤 (刷新顯示,正確檢測 HTTP 上的更改)、Android 2.x 設備上的問題、Google 雲端硬碟與微軟 OneDrive 的存取問題、關閉資料庫同時清除剪貼簿、打開附加檔案時的錯誤、鍵盤顯示的問題\n</string>
* 合併來自 Xamarin 的修Keepass2Android 現在與 Android 4.4.2 上的 ART 虛擬機相容。\n
* 修正錯誤:同步過程中的錯誤 (刷新顯示,正確檢測 HTTP 上的更改)、Android 2.x 裝置上的問題、Google 雲端硬碟與微軟 OneDrive 的存取實作錯誤、關閉資料庫同時清除剪貼簿、打開附加檔案時的錯誤、鍵盤顯示的問題\n</string>
<string name="ChangeLog_0_9_3"><b>版本 0.9.3</b>\n
* 新鍵盤包含許多改進。到設置個人化吧!\n
* 新增 kdb (Keepass 1) 檔案的唯讀支援。實驗中!\n
* 新增 SFTP 支援\n
* 添加 ART 虛擬機環境出錯的解決辦法 (Android 4.4.2)\n
* 其它修正\n</string>
* 修正其它錯誤\n</string>
<string name="ChangeLog_0_9_2"><b>版本 0.9.2</b>\n
* 新增 OTP 支援 (相容於 OtpKeyProv 外掛程式)\n
* 內建 NFC 支援,可从 YubiKey NEO 外掛獲取一次性密碼\n
@@ -920,7 +944,7 @@
* 集成 Keepass 2.24 程式庫\n
* 新增結束程式選單 (見設定)\n
* 改進 SSL 電子憑證驗證\n
* 其它問題修正\n</string>
* 修正其它錯誤\n</string>
<string name="ChangeLog_0_9_1"><b>版本 0.9.1</b>\n
*內建 SkyDrive 支援 (僅適用於 Keepass2Android 常規版)\n
*修正 Google 雲端硬碟的問題\n
@@ -930,20 +954,20 @@
* 內建檔案管理器 (仰賴於 HBA 的 android-filechooser)\n
* 改進新增資料庫時的使用者介面\n
* 自定義字體 DejaVu Sans Mono 用於顯示密碼\n
* 其它問題修正</string>
* 修正其它錯誤</string>
<string name="ChangeLog_0_8_6"><b>版本 0.8.6</b>\n
* 支援 Twofish 加密算法\n
* 允許編輯群組\n
* 允許移動項目和群組\n
* 快速解鎖圖示可以變成透明 (見設定)\n
* 其它問題修正</string>
* QuickUnlock 圖示可以變成透明 (見設定)\n
* 修正其它錯誤</string>
<string name="ChangeLog_0_8_5"><b>版本 0.8.5</b>\n
* 遠端檔案存儲於程式快取中以允許離線使用 (包括編輯和後續的同步),詳情參閱設。\n
* 通知圖示展現資料庫的鎖定狀態 (見設)\n
* 遠端檔案存儲於程式快取中以允許離線使用 (包括編輯和後續的同步),詳情參閱設。\n
* 通知圖示展現資料庫的鎖定狀態 (見設)\n
* 在某些情況下改進了鎖定狀態的確定\n
* 鍵入密碼時,資料庫會加載到記憶體中以提升解鎖速度 (見設)\n
* 條目可添加到根目錄\n
* 錯誤修正:解析引用欄位、意大利語和漢語設備上的鍵盤問題</string>
* 鍵入密碼時,資料庫會加載到記憶體中以提升解鎖速度 (見設)\n
* 可新增項目到根目錄\n
* 修正錯誤:解析引用欄位、意大利語和漢語裝置上的鍵盤問題</string>
<string name="ChangeLog_0_8_4"><b>版本 0.8.4</b>\n
* 保存資料庫時會偵測並合併外部更改\n
* 改進加載性能\n
@@ -969,15 +993,15 @@
* KP2A 離線版和常規版可同時被安裝\n
* 新增翻譯 (感謝所有譯者!)</string>
<string name="ChangeLog_0_8"><b>版本 0.8</b>\n
* 改進使用者介面,特別是 Android 4.x 設備\n
* 改進使用者介面,特別是 Android 4.x 裝置\n
* 可使用指定的檔案管理器\n
* 新增更安全的方式打開附加檔案 (透過快取)\n
* 修正編輯器中的錯誤\n
* 可能引入了新的 Bug :-)</string>
* 可能引入了新的錯誤 :-)</string>
<string name="ChangeLog_keptDonate">為了 KP2A 的未來起見,捐贈任何東西都有助其擴展無限可能</string>
<string name="ChangeLog_0_7"><b>版本 0.7</b>\n
* 提高載入速度:金鑰轉換現在快了十倍!\n
* 添加 Keepass2Android 虛擬鍵盤:切換到此鍵盤鍵入憑證,避免被監視剪貼簿的應用竊取密碼 (在設定中用舊的剪貼簿通知) \n
* 添加 Keepass2Android 虛擬鍵盤:切換到此鍵盤鍵入憑證,避免被監視剪貼簿的程式竊取密碼 (在設定中用舊的剪貼簿通知) \n
* 新增捐贈啤酒或其他東西的選項 (見選單)</string>
<string name="ChangeLog"><b>版本 0.6.2</b>\n
* Google Drive/Dropbox/... 整合:使用官方 Google Drive 或 Dropbox 應用程式並開啟任何 .kdbx 檔案,將自動開啟 KP2A。\n
@@ -1014,11 +1038,11 @@
<item></item>
</string-array>
<string-array name="design_options">
<item>色主題</item>
<item>色主題</item>
<item>色主題</item>
<item>色主題</item>
<item>系統設定</item>
</string-array>
<string name="design_title">設計</string>
<string name="design_title">主題</string>
<string-array name="ftp_encryption_modes">
<item>無加密 (FTP)</item>
<item>隱式加密 (透過 TLS 的 FTPFTPS)</item>
@@ -1055,7 +1079,7 @@
<string name="autofill_enable_failed">抱歉,您的裝置並未支援由應用程式打開系統設定。煩請親自到系統設定啟用自動填入服務。</string>
<string name="show_autofill_help">顯示自動填入的說明</string>
<string name="autofill_sign_in_prompt">以 Keepass2Android 填入</string>
<string name="autofill_disable">用 %1$s 的自動填入</string>
<string name="autofill_disable">用 %1$s 的自動填入</string>
<string name="autofill_enable_for">啟用 %1$s 的自動填入</string>
<string name="invalid_link_association">無法關聯網域 %1$s 與應用程式 %2$s</string>
<string name="enable_fingerprint_hint">Keepass2Android 偵測到生物識別硬件。要為這資料庫啟用生物識別解鎖嗎?</string>
@@ -1076,5 +1100,5 @@
<string name="AutofillWarning_title">安全警示:未知網域或應用程式的鏈結</string>
<string name="AutofillWarning_Intro">你正將「%1$s」網域的憑證輸入到「%2$s」應用程式。</string>
<string name="AutofillWarning_FillDomainInUntrustedApp">如你相信「%2$s」屬於「%1$s」或「%2$s」應用程式不會誤用憑證例如這是可信的瀏覽器請繼續。反之請取消。</string>
<string name="AutofillWarning_trustAsBrowser">一律同意「%1$s」</string>
<string name="AutofillWarning_trustAsBrowser">一律接受「%1$s」</string>
</resources>

View File

@@ -301,6 +301,8 @@
<string name="NoDalVerification_summary">不检查域名和应用程序包是否匹配</string>
<string name="InlineSuggestions_title">与键盘集成</string>
<string name="InlineSuggestions_summary">将自动填充建议显示为键盘中的内联选项(如果输入法支持的话)</string>
<string name="LogAutofillView_title">记录自动填充视图</string>
<string name="LogAutofillView_summary">将自动填充视图的详情写入调试日志 (如果启用了调试日志)。 如果自动填充无法正常工作,这些细节可以发送给开发者。</string>
<string name="requires_android11">需要 Android 11 或更高版本</string>
<string name="kp2a_findUrl">找回密码</string>
<string name="excludeExpiredEntries">排除过期的条目</string>
@@ -689,6 +691,7 @@
<item>修复崩溃和意外注销的 bug</item>
<item>切换到新的 SFTP 实现,支持现代公钥算法,例如 rsa-sha2-256</item>
<item>复制到剪贴板时将密码标记为敏感 (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">
<item>新增查看、移除和恢复条目备份的功能</item>

View File

@@ -54,6 +54,7 @@
<string name="AlwaysMergeOnConflict_key">AlwaysMergeOnConflict</string>
<string name="NoDalVerification_key">NoDalVerification_key</string>
<string name="InlineSuggestions_key">InlineSuggestions_key</string>
<string name="LogAutofillView_key">LogAutofillView_key</string>
<string name="algorithm_key">algorithm</string>
<string name="app_key">app</string>
<string name="app_timeout_key">app_timeout_key</string>

View File

@@ -332,6 +332,9 @@
<string name="InlineSuggestions_title">Integrate with keyboard</string>
<string name="InlineSuggestions_summary">Shows the autofill suggestions as inline options in the keyboard (if supported by the input method)</string>
<string name="LogAutofillView_title">Log autofill view</string>
<string name="LogAutofillView_summary">Write details about the autofill view to debug log (if debug logging is enabled). These details can be sent to the developer if autofill does not work as expected.</string>
<string name="requires_android11">Requires Android 11 or later</string>
<string name="kp2a_findUrl">Find password</string>
@@ -850,6 +853,7 @@
<item>Bug fix to crashes and unexpected log-outs</item>
<item>Switch to new SFTP implementation, supporting modern public key algorithms such as rsa-sha2-256</item>
<item>Mark passwords as sensitive when copying to clipboard (Android 13)</item>
<item>Autofill improvements</item>
</string-array>
<string-array name="ChangeLog_1_09d">

View File

@@ -454,6 +454,15 @@
android:title="@string/InlineSuggestions_title"
android:key="@string/InlineSuggestions_key" />
<CheckBoxPreference
android:enabled="true"
android:persistent="true"
android:summary="@string/LogAutofillView_summary"
android:defaultValue="false"
android:title="@string/LogAutofillView_title"
android:key="@string/LogAutofillView_key" />
<CheckBoxPreference
android:enabled="true"
android:persistent="true"

View File

@@ -1944,6 +1944,10 @@
<Project>{545b4a6b-8bba-4fbe-92fc-4ac060122a54}</Project>
<Name>KeePassLib2Android</Name>
</ProjectReference>
<ProjectReference Include="..\Kp2aAutofillParser\Kp2aAutofillParser.csproj">
<Project>{39b12571-bafe-4d3a-aee2-4d74f14dfd96}</Project>
<Name>Kp2aAutofillParser</Name>
</ProjectReference>
<ProjectReference Include="..\Kp2aBusinessLogic\Kp2aBusinessLogic.csproj">
<Project>{53a9cb7f-6553-4bc0-b56b-9410bb2e59aa}</Project>
<Name>Kp2aBusinessLogic</Name>

View File

@@ -5,6 +5,7 @@ using Android.App.Assist;
using Android.Service.Autofill;
using Android.Views;
using Android.Views.Autofill;
using Kp2aAutofillParser;
namespace keepass2android.services.AutofillBase
{
@@ -38,9 +39,8 @@ namespace keepass2android.services.AutofillBase
AutofillOptions = view.GetAutofillOptions();
Focused = view.IsFocused;
var supportedHints = AutofillHintsHelper.FilterForSupportedHints(autofillHints);
var canonicalHints = AutofillHintsHelper.ConvertToCanonicalHints(supportedHints);
var canonicalHints = AutofillHintsHelper.ConvertToCanonicalLowerCaseHints(supportedHints);
SetHints(canonicalHints.ToArray());
}
void SetHints(string[] value)

View File

@@ -7,11 +7,12 @@ using Android.Runtime;
using Android.Service.Autofill;
using Android.Util;
using Android.Views;
using Android.Views.Autofill;
using Android.Widget;
using Android.Widget.Inline;
using AndroidX.AutoFill.Inline;
using AndroidX.AutoFill.Inline.V1;
using FilledAutofillFieldCollection = keepass2android.services.AutofillBase.model.FilledAutofillFieldCollection;
using Kp2aAutofillParser;
namespace keepass2android.services.AutofillBase
{
@@ -93,13 +94,9 @@ namespace keepass2android.services.AutofillBase
/// Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the
/// client View.
/// </summary>
/// <returns>The dataset.</returns>
/// <param name="context">Context.</param>
/// <param name="autofillFields">Autofill fields.</param>
/// <param name="filledAutofillFieldCollection">Filled autofill field collection.</param>
public static Dataset NewDataset(Context context,
AutofillFieldMetadataCollection autofillFields,
FilledAutofillFieldCollection filledAutofillFieldCollection,
FilledAutofillFieldCollection<ViewNodeInputField> filledAutofillFieldCollection,
IAutofillIntentBuilder intentBuilder,
Android.Widget.Inline.InlinePresentationSpec inlinePresentationSpec)
{
@@ -108,21 +105,115 @@ namespace keepass2android.services.AutofillBase
var datasetBuilder = new Dataset.Builder(NewRemoteViews(context.PackageName, datasetName, intentBuilder.AppIconResource));
datasetBuilder.SetId(datasetName);
var setValueAtLeastOnce = filledAutofillFieldCollection.ApplyToFields(autofillFields, datasetBuilder);
var setValueAtLeastOnce = ApplyToFields(filledAutofillFieldCollection, autofillFields, datasetBuilder);
AddInlinePresentation(context, inlinePresentationSpec, datasetName, datasetBuilder, intentBuilder.AppIconResource, null);
if (setValueAtLeastOnce)
{
return datasetBuilder.Build();
}
else
/*else
{
Kp2aLog.Log("Failed to set at least one value. #fields=" + autofillFields.GetAutofillIds().Length + " " + autofillFields.FocusedAutofillCanonicalHints);
}
}*/
return null;
}
/// <summary>
/// Populates a Dataset.Builder with appropriate values for each AutofillId
/// in a AutofillFieldMetadataCollection.
///
/// In other words, it constructs an autofill Dataset.Builder
/// by applying saved values (from this FilledAutofillFieldCollection)
/// to Views specified in a AutofillFieldMetadataCollection, which represents the current
/// page the user is on.
/// </summary>
/// <returns><c>true</c>, if to fields was applyed, <c>false</c> otherwise.</returns>
/// <param name="filledAutofillFieldCollection"></param>
/// <param name="autofillFieldMetadataCollection">Autofill field metadata collection.</param>
/// <param name="datasetBuilder">Dataset builder.</param>
public static bool ApplyToFields(FilledAutofillFieldCollection<ViewNodeInputField> filledAutofillFieldCollection,
AutofillFieldMetadataCollection autofillFieldMetadataCollection, Dataset.Builder datasetBuilder)
{
bool setValueAtLeastOnce = false;
foreach (string hint in autofillFieldMetadataCollection.AllAutofillCanonicalHints)
{
foreach (AutofillFieldMetadata autofillFieldMetadata in autofillFieldMetadataCollection.GetFieldsForHint(hint))
{
FilledAutofillField<ViewNodeInputField> filledAutofillField;
if (!filledAutofillFieldCollection.HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null))
{
continue;
}
var autofillId = autofillFieldMetadata.AutofillId;
var autofillType = autofillFieldMetadata.AutofillType;
switch (autofillType)
{
case AutofillType.List:
var listValue = autofillFieldMetadata.GetAutofillOptionIndex(filledAutofillField.TextValue);
if (listValue != -1)
{
datasetBuilder.SetValue(autofillId, AutofillValue.ForList(listValue));
setValueAtLeastOnce = true;
}
break;
case AutofillType.Date:
var dateValue = filledAutofillField.DateValue;
datasetBuilder.SetValue(autofillId, AutofillValue.ForDate((long)dateValue));
setValueAtLeastOnce = true;
break;
case AutofillType.Text:
var textValue = filledAutofillField.TextValue;
if (textValue != null)
{
datasetBuilder.SetValue(autofillId, AutofillValue.ForText(textValue));
setValueAtLeastOnce = true;
}
break;
case AutofillType.Toggle:
var toggleValue = filledAutofillField.ToggleValue;
if (toggleValue != null)
{
datasetBuilder.SetValue(autofillId, AutofillValue.ForToggle(toggleValue.Value));
setValueAtLeastOnce = true;
}
break;
default:
Log.Warn(CommonUtil.Tag, "Invalid autofill type - " + autofillType);
break;
}
}
}
/*
if (!setValueAtLeastOnce)
{
Kp2aLog.Log("No value set. Hint keys : " + string.Join(",", HintMap.Keys));
foreach (string hint in autofillFieldMetadataCollection.AllAutofillCanonicalHints)
{
Kp2aLog.Log("No value set. Hint = " + hint);
foreach (AutofillFieldMetadata autofillFieldMetadata in autofillFieldMetadataCollection
.GetFieldsForHint(hint))
{
Kp2aLog.Log("No value set. fieldForHint = " + autofillFieldMetadata.AutofillId.ToString());
FilledAutofillField filledAutofillField;
if (!HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null))
{
Kp2aLog.Log("No value set. Hint map does not contain value, " +
(filledAutofillField == null));
continue;
}
Kp2aLog.Log("autofill type=" + autofillFieldMetadata.AutofillType);
}
}
}*/
return setValueAtLeastOnce;
}
public static void AddInlinePresentation(Context context, InlinePresentationSpec inlinePresentationSpec,
string datasetName, Dataset.Builder datasetBuilder, int iconId, PendingIntent pendingIntent)
{

View File

@@ -14,256 +14,5 @@ using keepass2android.services.AutofillBase.model;
namespace keepass2android.services.AutofillBase
{
class AutofillHintsHelper
{
private static readonly HashSet<string> _allSupportedHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
View.AutofillHintCreditCardExpirationDate,
View.AutofillHintCreditCardExpirationDay,
View.AutofillHintCreditCardExpirationMonth,
View.AutofillHintCreditCardExpirationYear,
View.AutofillHintCreditCardNumber,
View.AutofillHintCreditCardSecurityCode,
View.AutofillHintEmailAddress,
View.AutofillHintPhone,
View.AutofillHintName,
View.AutofillHintPassword,
View.AutofillHintPostalAddress,
View.AutofillHintPostalCode,
View.AutofillHintUsername,
W3cHints.HONORIFIC_PREFIX,
W3cHints.NAME,
W3cHints.GIVEN_NAME,
W3cHints.ADDITIONAL_NAME,
W3cHints.FAMILY_NAME,
W3cHints.HONORIFIC_SUFFIX,
W3cHints.USERNAME,
W3cHints.NEW_PASSWORD,
W3cHints.CURRENT_PASSWORD,
W3cHints.ORGANIZATION_TITLE,
W3cHints.ORGANIZATION,
W3cHints.STREET_ADDRESS,
W3cHints.ADDRESS_LINE1,
W3cHints.ADDRESS_LINE2,
W3cHints.ADDRESS_LINE3,
W3cHints.ADDRESS_LEVEL4,
W3cHints.ADDRESS_LEVEL3,
W3cHints.ADDRESS_LEVEL2,
W3cHints.ADDRESS_LEVEL1,
W3cHints.COUNTRY,
W3cHints.COUNTRY_NAME,
W3cHints.POSTAL_CODE,
W3cHints.CC_NAME,
W3cHints.CC_GIVEN_NAME,
W3cHints.CC_ADDITIONAL_NAME,
W3cHints.CC_FAMILY_NAME,
W3cHints.CC_NUMBER,
W3cHints.CC_EXPIRATION,
W3cHints.CC_EXPIRATION_MONTH,
W3cHints.CC_EXPIRATION_YEAR,
W3cHints.CC_CSC,
W3cHints.CC_TYPE,
W3cHints.TRANSACTION_CURRENCY,
W3cHints.TRANSACTION_AMOUNT,
W3cHints.LANGUAGE,
W3cHints.BDAY,
W3cHints.BDAY_DAY,
W3cHints.BDAY_MONTH,
W3cHints.BDAY_YEAR,
W3cHints.SEX,
W3cHints.URL,
W3cHints.PHOTO,
W3cHints.TEL,
W3cHints.TEL_COUNTRY_CODE,
W3cHints.TEL_NATIONAL,
W3cHints.TEL_AREA_CODE,
W3cHints.TEL_LOCAL,
W3cHints.TEL_LOCAL_PREFIX,
W3cHints.TEL_LOCAL_SUFFIX,
W3cHints.TEL_EXTENSION,
W3cHints.EMAIL,
W3cHints.IMPP,
};
private static readonly List<HashSet<string>> partitionsOfCanonicalHints = new List<HashSet<string>>()
{
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
View.AutofillHintEmailAddress,
View.AutofillHintPhone,
View.AutofillHintName,
View.AutofillHintPassword,
View.AutofillHintUsername,
W3cHints.HONORIFIC_PREFIX,
W3cHints.NAME,
W3cHints.GIVEN_NAME,
W3cHints.ADDITIONAL_NAME,
W3cHints.FAMILY_NAME,
W3cHints.HONORIFIC_SUFFIX,
W3cHints.ORGANIZATION_TITLE,
W3cHints.ORGANIZATION,
W3cHints.LANGUAGE,
W3cHints.BDAY,
W3cHints.BDAY_DAY,
W3cHints.BDAY_MONTH,
W3cHints.BDAY_YEAR,
W3cHints.SEX,
W3cHints.URL,
W3cHints.PHOTO,
W3cHints.TEL,
W3cHints.TEL_COUNTRY_CODE,
W3cHints.TEL_NATIONAL,
W3cHints.TEL_AREA_CODE,
W3cHints.TEL_LOCAL,
W3cHints.TEL_LOCAL_PREFIX,
W3cHints.TEL_LOCAL_SUFFIX,
W3cHints.TEL_EXTENSION,
W3cHints.IMPP,
},
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
View.AutofillHintPostalAddress,
View.AutofillHintPostalCode,
W3cHints.STREET_ADDRESS,
W3cHints.ADDRESS_LINE1,
W3cHints.ADDRESS_LINE2,
W3cHints.ADDRESS_LINE3,
W3cHints.ADDRESS_LEVEL4,
W3cHints.ADDRESS_LEVEL3,
W3cHints.ADDRESS_LEVEL2,
W3cHints.ADDRESS_LEVEL1,
W3cHints.COUNTRY,
W3cHints.COUNTRY_NAME
},
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
View.AutofillHintCreditCardExpirationDate,
View.AutofillHintCreditCardExpirationDay,
View.AutofillHintCreditCardExpirationMonth,
View.AutofillHintCreditCardExpirationYear,
View.AutofillHintCreditCardNumber,
View.AutofillHintCreditCardSecurityCode,
W3cHints.CC_NAME,
W3cHints.CC_GIVEN_NAME,
W3cHints.CC_ADDITIONAL_NAME,
W3cHints.CC_FAMILY_NAME,
W3cHints.CC_TYPE,
W3cHints.TRANSACTION_CURRENCY,
W3cHints.TRANSACTION_AMOUNT,
},
};
private static readonly Dictionary<string, string> hintToCanonicalReplacement= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{W3cHints.EMAIL, View.AutofillHintEmailAddress},
{W3cHints.USERNAME, View.AutofillHintUsername},
{W3cHints.CURRENT_PASSWORD, View.AutofillHintPassword},
{W3cHints.NEW_PASSWORD, View.AutofillHintPassword},
{W3cHints.CC_EXPIRATION_MONTH, View.AutofillHintCreditCardExpirationMonth },
{W3cHints.CC_EXPIRATION_YEAR, View.AutofillHintCreditCardExpirationYear },
{W3cHints.CC_EXPIRATION, View.AutofillHintCreditCardExpirationDate },
{W3cHints.CC_NUMBER, View.AutofillHintCreditCardNumber },
{W3cHints.CC_CSC, View.AutofillHintCreditCardSecurityCode },
{W3cHints.POSTAL_CODE, View.AutofillHintPostalCode },
};
public static bool IsSupportedHint(string hint)
{
return _allSupportedHints.Contains(hint);
}
public static string[] FilterForSupportedHints(string[] hints)
{
var filteredHints = new string[hints.Length];
int i = 0;
foreach (var hint in hints)
{
if (IsSupportedHint(hint))
{
filteredHints[i++] = hint;
}
else
{
CommonUtil.logd("Invalid autofill hint: " + hint);
}
}
var finalFilteredHints = new string[i];
Array.Copy(filteredHints, 0, finalFilteredHints, 0, i);
return finalFilteredHints;
}
/// <summary>
/// transforms hints by replacing some W3cHints by their Android counterparts and transforming everything to lowercase
/// </summary>
public static List<string> ConvertToCanonicalHints(string[] supportedHints)
{
List<string> result = new List<string>();
foreach (string hint in supportedHints)
{
string canonicalHint;
if (!hintToCanonicalReplacement.TryGetValue(hint, out canonicalHint))
canonicalHint = hint;
result.Add(canonicalHint.ToLower());
}
return result;
}
public static int GetPartitionIndex(string hint)
{
for (int i = 0; i < partitionsOfCanonicalHints.Count; i++)
{
if (partitionsOfCanonicalHints[i].Contains(hint))
{
return i;
}
}
return -1;
}
public static FilledAutofillFieldCollection FilterForPartition(FilledAutofillFieldCollection autofillFields, int partitionIndex)
{
FilledAutofillFieldCollection filteredCollection =
new FilledAutofillFieldCollection {DatasetName = autofillFields.DatasetName};
if (partitionIndex == -1)
return filteredCollection;
foreach (var field in autofillFields.HintMap.Values.Distinct())
{
foreach (var hint in field.AutofillHints)
{
if (GetPartitionIndex(hint) == partitionIndex)
{
filteredCollection.Add(field);
break;
}
}
}
return filteredCollection;
}
public static FilledAutofillFieldCollection FilterForPartition(FilledAutofillFieldCollection filledAutofillFieldCollection, List<string> autofillFieldsFocusedAutofillCanonicalHints)
{
//only apply partition data if we have FocusedAutofillCanonicalHints. This may be empty on buggy Firefox.
if (autofillFieldsFocusedAutofillCanonicalHints.Any())
{
int partitionIndex = AutofillHintsHelper.GetPartitionIndex(autofillFieldsFocusedAutofillCanonicalHints.FirstOrDefault());
return AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, partitionIndex);
}
return filledAutofillFieldCollection;
}
}
}

View File

@@ -20,6 +20,7 @@ using AndroidX.AutoFill.Inline;
using AndroidX.AutoFill.Inline.V1;
using Java.Util.Concurrent.Atomic;
using keepass2android.services.AutofillBase.model;
using Kp2aAutofillParser;
namespace keepass2android.services.AutofillBase
{
@@ -137,7 +138,7 @@ namespace keepass2android.services.AutofillBase
return;
}
AutofillFieldMetadataCollection autofillFields = parser.AutofillFields;
InlineSuggestionsRequest inlineSuggestionsRequest = null;
IList<InlinePresentationSpec> inlinePresentationSpecs = null;
if (((int) Build.VERSION.SdkInt >= 30)
@@ -149,7 +150,7 @@ namespace keepass2android.services.AutofillBase
}
var autofillIds = autofillFields.GetAutofillIds();
var autofillIds = parser.AutofillFields.GetAutofillIds();
if (autofillIds.Length != 0 && CanAutofill(query, isManual))
{
var responseBuilder = new FillResponse.Builder();
@@ -255,7 +256,7 @@ namespace keepass2android.services.AutofillBase
if (warning == DisplayWarning.None)
{
FilledAutofillFieldCollection partitionData =
FilledAutofillFieldCollection<ViewNodeInputField> partitionData =
AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, parser.AutofillFields.FocusedAutofillCanonicalHints);
Kp2aLog.Log("AF: Add dataset");
@@ -299,7 +300,7 @@ namespace keepass2android.services.AutofillBase
}
protected abstract List<FilledAutofillFieldCollection> GetSuggestedEntries(string query);
protected abstract List<FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query);
public enum DisplayWarning
{

View File

@@ -12,6 +12,7 @@ using Java.Util;
using keepass2android.services.AutofillBase.model;
using System.Linq;
using Android.Content.PM;
using Kp2aAutofillParser;
#if !NoNet
using Com.Dropbox.Core.V2.Teamlog;
#endif
@@ -173,7 +174,7 @@ namespace keepass2android.services.AutofillBase
ReplyIntent = null;
}
protected void OnSuccess(FilledAutofillFieldCollection clientFormDataMap, bool isManual)
protected void OnSuccess(FilledAutofillFieldCollection<ViewNodeInputField> clientFormDataMap, bool isManual)
{
var intent = Intent;
AssistStructure structure = (AssistStructure)intent.GetParcelableExtra(AutofillManager.ExtraAssistStructure);
@@ -229,7 +230,7 @@ namespace keepass2android.services.AutofillBase
/// <summary>
/// Creates the FilledAutofillFieldCollection from the intent returned from the query activity
/// </summary>
protected abstract FilledAutofillFieldCollection GetDataset();
protected abstract FilledAutofillFieldCollection<ViewNodeInputField> GetDataset();
public abstract IAutofillIntentBuilder IntentBuilder { get; }

View File

@@ -2,11 +2,13 @@
using System.Linq;
using Android.Content;
using Android.Preferences;
using Kp2aAutofillParser;
namespace keepass2android.services.AutofillBase
{
internal class Kp2aDigitalAssetLinksDataSource
internal class Kp2aDigitalAssetLinksDataSource : IKp2aDigitalAssetLinksDataSource
{
private const string Autofilltrustedapps = "AutoFillTrustedApps";
@@ -37,6 +39,11 @@ namespace keepass2android.services.AutofillBase
return trustedLinks.Contains(BuildLink(domain, targetPackage));
}
public bool IsEnabled()
{
return !PreferenceManager.GetDefaultSharedPreferences(_ctx).GetBoolean(_ctx.GetString(Resource.String.NoDalVerification_key), false);
}
public void RememberAsTrustedApp(string packageName)
{
var prefs = PreferenceManager.GetDefaultSharedPreferences(_ctx);

View File

@@ -1,334 +1,201 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Android.App.Assist;
using Android.Content;
using Android.Preferences;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Autofill;
using Android.Views.InputMethods;
using DomainNameParser;
using keepass2android.services.AutofillBase.model;
using FilledAutofillFieldCollection = keepass2android.services.AutofillBase.model.FilledAutofillFieldCollection;
using Kp2aAutofillParser;
using Newtonsoft.Json;
namespace keepass2android.services.AutofillBase
{
public class ViewNodeInputField : Kp2aAutofillParser.InputField
{
public ViewNodeInputField(AssistStructure.ViewNode viewNode)
{
ViewNode = viewNode;
IdEntry = viewNode.IdEntry;
Hint = viewNode.Hint;
ClassName = viewNode.ClassName;
AutofillHints = viewNode.GetAutofillHints();
IsFocused = viewNode.IsFocused;
InputType = (Kp2aAutofillParser.InputTypes) ((int)viewNode.InputType);
HtmlInfoTag = viewNode.HtmlInfo?.Tag;
HtmlInfoTypeAttribute = viewNode.HtmlInfo?.Attributes?.FirstOrDefault(p => p.First?.ToString() == "type")?.Second?.ToString();
}
[JsonIgnore]
public AssistStructure.ViewNode ViewNode { get; set; }
public void FillFilledAutofillValue(FilledAutofillField<ViewNodeInputField> filledField)
{
AutofillValue autofillValue = ViewNode.AutofillValue;
if (autofillValue != null)
{
if (autofillValue.IsList)
{
string[] autofillOptions = ViewNode.GetAutofillOptions();
int index = autofillValue.ListValue;
if (autofillOptions != null && autofillOptions.Length > 0)
{
filledField.TextValue = autofillOptions[index];
}
}
else if (autofillValue.IsDate)
{
filledField.DateValue = autofillValue.DateValue;
}
else if (autofillValue.IsText)
{
filledField.TextValue = autofillValue.TextValue;
}
}
}
}
/// <summary>
/// Converts an AssistStructure into a list of InputFields
/// </summary>
class AutofillViewFromAssistStructureFinder
{
private readonly Context _context;
private readonly AssistStructure _structure;
private PublicSuffixRuleCache domainSuffixParserCache;
public AutofillViewFromAssistStructureFinder(Context context, AssistStructure structure)
{
_context = context;
_structure = structure;
domainSuffixParserCache = new PublicSuffixRuleCache(context);
}
public AutofillView<ViewNodeInputField> GetAutofillView(bool isManualRequest)
{
AutofillView<ViewNodeInputField> autofillView = new AutofillView<ViewNodeInputField>();
int nodeCount = _structure.WindowNodeCount;
for (int i = 0; i < nodeCount; i++)
{
var node = _structure.GetWindowNodeAt(i);
var view = node.RootViewNode;
ParseRecursive(autofillView, view, isManualRequest);
}
return autofillView;
}
void ParseRecursive(AutofillView<ViewNodeInputField> autofillView, AssistStructure.ViewNode viewNode, bool isManualRequest)
{
String webDomain = viewNode.WebDomain;
if ((autofillView.PackageId == null) && (!string.IsNullOrWhiteSpace(viewNode.IdPackage)) &&
(viewNode.IdPackage != "android"))
{
autofillView.PackageId = viewNode.IdPackage;
}
DomainName outDomain;
if (DomainName.TryParse(webDomain, domainSuffixParserCache, out outDomain))
{
webDomain = outDomain.RawDomainName;
}
if (webDomain != null)
{
if (!string.IsNullOrEmpty(autofillView.WebDomain))
{
if (webDomain != autofillView.WebDomain)
{
throw new Java.Lang.SecurityException($"Found multiple web domains: valid= {autofillView.WebDomain}, child={webDomain}");
}
}
else
{
autofillView.WebDomain = webDomain;
}
}
autofillView.InputFields.Add(new ViewNodeInputField(viewNode));
var childrenSize = viewNode.ChildCount;
if (childrenSize > 0)
{
for (int i = 0; i < childrenSize; i++)
{
ParseRecursive(autofillView, viewNode.GetChildAt(i), isManualRequest);
}
}
}
}
/// <summary>
/// Parser for an AssistStructure object. This is invoked when the Autofill Service receives an
/// AssistStructure from the client Activity, representing its View hierarchy. In this sample, it
/// parses the hierarchy and collects autofill metadata from {@link ViewNode}s along the way.
/// </summary>
public sealed class StructureParser
public sealed class StructureParser: StructureParserBase<ViewNodeInputField>
{
public Context mContext { get; }
private readonly AssistStructure _structure;
public Context _context { get; }
public AutofillFieldMetadataCollection AutofillFields { get; set; }
AssistStructure Structure;
private List<AssistStructure.ViewNode> _editTextsWithoutHint = new List<AssistStructure.ViewNode>();
private PublicSuffixRuleCache domainSuffixParserCache;
public FilledAutofillFieldCollection ClientFormData { get; set; }
public FilledAutofillFieldCollection<ViewNodeInputField> ClientFormData { get; set; }
public string PackageId { get; set; }
public StructureParser(Context context, AssistStructure structure)
: base(new Kp2aLogger(), new Kp2aDigitalAssetLinksDataSource(context))
{
kp2aDigitalAssetLinksDataSource = new Kp2aDigitalAssetLinksDataSource(context);
mContext = context;
Structure = structure;
AutofillFields = new AutofillFieldMetadataCollection();
domainSuffixParserCache = new PublicSuffixRuleCache(context);
}
_context = context;
_structure = structure;
AutofillFields = new AutofillFieldMetadataCollection();
LogAutofillView = PreferenceManager.GetDefaultSharedPreferences(context).GetBoolean(context.GetString(Resource.String.LogAutofillView_key), false);
public class AutofillTargetId
{
public string PackageName { get; set; }
public string PackageNameWithPseudoSchema
{
get { return KeePass.AndroidAppScheme + PackageName; }
}
public string WebDomain { get; set; }
/// <summary>
/// If PackageName and WebDomain are not compatible (by DAL or because PackageName is a trusted browser in which case we treat all domains as "compatible"
/// we need to issue a warning. If we would fill credentials for the package, a malicious website could try to get credentials for the app.
/// If we would fill credentials for the domain, a malicious app could get credentials for the domain.
/// </summary>
public bool IncompatiblePackageAndDomain { get; set; }
public string DomainOrPackage
{
get
{
return WebDomain ?? PackageNameWithPseudoSchema;
}
}
}
public AutofillTargetId ParseForFill(bool isManual)
{
return Parse(true, isManual);
}
public AutofillTargetId ParseForSave()
{
return Parse(false, true);
}
/// <summary>
/// Traverse AssistStructure and add ViewNode metadata to a flat list.
/// </summary>
/// <returns>The parse.</returns>
/// <param name="forFill">If set to <c>true</c> for fill.</param>
/// <param name="isManualRequest"></param>
AutofillTargetId Parse(bool forFill, bool isManualRequest)
protected override AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<ViewNodeInputField> autofillView)
{
AutofillTargetId result = new AutofillTargetId();
CommonUtil.logd("Parsing structure for " + Structure.ActivityComponent);
var nodes = Structure.WindowNodeCount;
ClientFormData = new FilledAutofillFieldCollection();
String webDomain = null;
_editTextsWithoutHint.Clear();
for (int i = 0; i < nodes; i++)
{
var node = Structure.GetWindowNodeAt(i);
var view = node.RootViewNode;
ParseLocked(forFill, isManualRequest, view, ref webDomain);
}
List<AssistStructure.ViewNode> passwordFields = new List<AssistStructure.ViewNode>();
List<AssistStructure.ViewNode> usernameFields = new List<AssistStructure.ViewNode>();
if (AutofillFields.Empty)
{
passwordFields = _editTextsWithoutHint.Where(IsPassword).ToList();
if (!passwordFields.Any())
{
passwordFields = _editTextsWithoutHint.Where(HasPasswordHint).ToList();
}
usernameFields = _editTextsWithoutHint.Where(HasUsernameHint).ToList();
if (usernameFields.Any() == false)
{
foreach (var passwordField in passwordFields)
{
var usernameField = _editTextsWithoutHint
.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault();
if (usernameField != null)
{
usernameFields.Add(usernameField);
}
}
}
if (usernameFields.Any() == false)
{
//for some pages with two-step login, we don't see a password field and don't display the autofill for non-manual requests. But if the user forces autofill,
//let's assume it is a username field:
if (isManualRequest && !passwordFields.Any() && _editTextsWithoutHint.Count == 1)
{
usernameFields.Add(_editTextsWithoutHint.First());
}
}
var result = base.Parse(forFill, isManualRequest, autofillView);
if (forFill)
{
foreach (var p in FieldsMappedToHints)
AutofillFields.Add(new AutofillFieldMetadata(p.Key.ViewNode, p.Value));
}
//force focused fields to be included in autofill fields when request was triggered manually. This allows to fill fields which are "off" or don't have a hint (in case there are hints)
if (isManualRequest)
{
foreach (AssistStructure.ViewNode editText in _editTextsWithoutHint)
{
if (editText.IsFocused)
{
if (IsPassword(editText) || HasPasswordHint(editText))
passwordFields.Add(editText);
else
usernameFields.Add(editText);
break;
}
}
}
if (forFill)
{
foreach (var uf in usernameFields)
AutofillFields.Add(new AutofillFieldMetadata(uf, new[] { View.AutofillHintUsername }));
foreach (var pf in passwordFields)
AutofillFields.Add(new AutofillFieldMetadata(pf, new[] { View.AutofillHintPassword }));
}
else
{
foreach (var uf in usernameFields)
ClientFormData.Add(new FilledAutofillField(uf, new[] { View.AutofillHintUsername }));
foreach (var pf in passwordFields)
ClientFormData.Add(new FilledAutofillField(pf, new[] { View.AutofillHintPassword }));
}
result.WebDomain = webDomain;
result.PackageName = Structure.ActivityComponent.PackageName;
if (!string.IsNullOrEmpty(webDomain) && !PreferenceManager.GetDefaultSharedPreferences(mContext).GetBoolean(mContext.GetString(Resource.String.NoDalVerification_key), false))
{
result.IncompatiblePackageAndDomain = !kp2aDigitalAssetLinksDataSource.IsTrustedLink(webDomain, result.PackageName);
if (result.IncompatiblePackageAndDomain)
{
CommonUtil.loge($"DAL verification failed for {result.PackageName}/{result.WebDomain}");
}
}
else
{
result.IncompatiblePackageAndDomain = false;
foreach (var p in FieldsMappedToHints)
ClientFormData.Add(new FilledAutofillField<ViewNodeInputField>(p.Key, p.Value));
}
return result;
}
private static readonly HashSet<string> _passwordHints = new HashSet<string> { "password","passwort" };
private static bool HasPasswordHint(AssistStructure.ViewNode f)
{
return ContainsAny(f.IdEntry, _passwordHints) ||
ContainsAny(f.Hint, _passwordHints);
}
private static readonly HashSet<string> _usernameHints = new HashSet<string> { "email","e-mail","username" };
private Kp2aDigitalAssetLinksDataSource kp2aDigitalAssetLinksDataSource;
private static bool HasUsernameHint(AssistStructure.ViewNode f)
public AutofillTargetId ParseForSave()
{
return ContainsAny(f.IdEntry, _usernameHints) ||
ContainsAny(f.Hint, _usernameHints);
var autofillView = new AutofillViewFromAssistStructureFinder(_context, _structure).GetAutofillView(true);
return Parse(false, true, autofillView);
}
private static bool ContainsAny(string value, IEnumerable<string> terms)
public StructureParserBase<ViewNodeInputField>.AutofillTargetId ParseForFill(bool isManual)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
var lowerValue = value.ToLowerInvariant();
return terms.Any(t => lowerValue.Contains(t));
var autofillView = new AutofillViewFromAssistStructureFinder(_context, _structure).GetAutofillView(isManual);
return Parse(true, isManual, autofillView);
}
private static bool IsInputTypeClass(InputTypes inputType, InputTypes inputTypeClass)
{
if (!InputTypes.MaskClass.HasFlag(inputTypeClass))
throw new Exception("invalid inputTypeClas");
return (((int)inputType) & (int)InputTypes.MaskClass) == (int) (inputTypeClass);
}
private static bool IsInputTypeVariation(InputTypes inputType, InputTypes inputTypeVariation)
{
if (!InputTypes.MaskVariation.HasFlag(inputTypeVariation))
throw new Exception("invalid inputTypeVariation");
bool result = (((int)inputType) & (int)InputTypes.MaskVariation) == (int)(inputTypeVariation);
if (result)
Kp2aLog.Log("found " + ((int)inputTypeVariation).ToString("X") + " in " + ((int)inputType).ToString("X"));
return result;
}
}
private static bool IsPassword(AssistStructure.ViewNode f)
{
InputTypes inputType = f.InputType;
return
(!f.IdEntry?.ToLowerInvariant().Contains("search") ?? true) &&
(!f.Hint?.ToLowerInvariant().Contains("search") ?? true) &&
(
(IsInputTypeClass(inputType, InputTypes.ClassText)
&&
(
IsInputTypeVariation(inputType, InputTypes.TextVariationPassword)
|| IsInputTypeVariation(inputType, InputTypes.TextVariationVisiblePassword)
|| IsInputTypeVariation(inputType, InputTypes.TextVariationWebPassword)
)
)
|| (f.HtmlInfo?.Attributes.Any(p => p.First.ToString() == "type" && p.Second.ToString() == "password") ?? false)
);
}
void ParseLocked(bool forFill, bool isManualRequest, AssistStructure.ViewNode viewNode, ref string validWebdomain)
{
String webDomain = viewNode.WebDomain;
if ((PackageId == null) && (!string.IsNullOrWhiteSpace(viewNode.IdPackage)) &&
(viewNode.IdPackage != "android"))
{
PackageId = viewNode.IdPackage;
}
DomainName outDomain;
if (DomainName.TryParse(webDomain, domainSuffixParserCache, out outDomain))
{
webDomain = outDomain.RawDomainName;
}
if (webDomain != null)
{
if (!string.IsNullOrEmpty(validWebdomain))
{
if (webDomain != validWebdomain)
{
throw new Java.Lang.SecurityException($"Found multiple web domains: valid= {validWebdomain}, child={webDomain}");
}
}
else
{
validWebdomain = webDomain;
}
}
string[] viewHints = viewNode.GetAutofillHints();
if (viewHints != null && viewHints.Length == 1 && viewHints.First() == "off" && viewNode.IsFocused &&
isManualRequest)
viewHints[0] = "on";
/*if (viewHints != null && viewHints.Any())
{
CommonUtil.logd("viewHints=" + viewHints);
CommonUtil.logd("class=" + viewNode.ClassName);
CommonUtil.logd("tag=" + (viewNode?.HtmlInfo?.Tag ?? "(null)"));
}*/
if (viewHints != null && viewHints.Length > 0 && viewHints.First() != "on" /*if hint is "on", treat as if there is no hint*/)
{
if (forFill)
{
AutofillFields.Add(new AutofillFieldMetadata(viewNode));
}
else
{
FilledAutofillField filledAutofillField = new FilledAutofillField(viewNode);
ClientFormData.Add(filledAutofillField);
}
}
else
{
if (viewNode.ClassName == "android.widget.EditText"
|| viewNode.ClassName == "android.widget.AutoCompleteTextView"
|| viewNode?.HtmlInfo?.Tag == "input")
{
_editTextsWithoutHint.Add(viewNode);
}
}
var childrenSize = viewNode.ChildCount;
if (childrenSize > 0)
{
for (int i = 0; i < childrenSize; i++)
{
ParseLocked(forFill, isManualRequest, viewNode.GetChildAt(i), ref validWebdomain);
}
}
}
}
public class Kp2aLogger : ILogger
{
public void Log(string x)
{
Kp2aLog.Log(x);
}
}
}

View File

@@ -2,154 +2,9 @@
using Android.App.Assist;
using Android.Views.Autofill;
using KeePassLib.Utility;
using Kp2aAutofillParser;
namespace keepass2android.services.AutofillBase.model
{
public class FilledAutofillField
{
private string[] _autofillHints;
public string TextValue { get; set; }
public long? DateValue { get; set; }
public bool? ToggleValue { get; set; }
public string ValueToString()
{
if (DateValue != null)
{
return TimeUtil.ConvertUnixTime((long)DateValue / 1000.0).ToLongDateString();
}
if (ToggleValue != null)
return ToggleValue.ToString();
return TextValue;
}
/// <summary>
/// returns the autofill hints for the filled field. These are always lowercased for simpler string comparison.
/// </summary>
public string[] AutofillHints
{
get
{
return _autofillHints;
}
set
{
_autofillHints = value;
for (int i = 0; i < _autofillHints.Length; i++)
_autofillHints[i] = _autofillHints[i].ToLower();
}
}
public FilledAutofillField()
{}
public FilledAutofillField(AssistStructure.ViewNode viewNode)
: this(viewNode, viewNode.GetAutofillHints())
{
}
public FilledAutofillField(AssistStructure.ViewNode viewNode, string[] hints)
{
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(hints);
List<string> hintList = new List<string>();
string nextHint = null;
for (int i = 0; i < rawHints.Length; i++)
{
string hint = rawHints[i];
if (i < rawHints.Length - 1)
{
nextHint = rawHints[i + 1];
}
// First convert the compound W3C autofill hints
if (W3cHints.isW3cSectionPrefix(hint) && i < rawHints.Length - 1)
{
hint = rawHints[++i];
CommonUtil.logd($"Hint is a W3C section prefix; using {hint} instead");
if (i < rawHints.Length - 1)
{
nextHint = rawHints[i + 1];
}
}
if (W3cHints.isW3cTypePrefix(hint) && nextHint != null && W3cHints.isW3cTypeHint(nextHint))
{
hint = nextHint;
i++;
CommonUtil.logd($"Hint is a W3C type prefix; using {hint} instead");
}
if (W3cHints.isW3cAddressType(hint) && nextHint != null)
{
hint = nextHint;
i++;
CommonUtil.logd($"Hint is a W3C address prefix; using {hint} instead");
}
// Then check if the "actual" hint is supported.
if (AutofillHintsHelper.IsSupportedHint(hint))
{
hintList.Add(hint);
}
else
{
CommonUtil.loge($"Invalid hint: {rawHints[i]}");
}
}
AutofillHints = AutofillHintsHelper.ConvertToCanonicalHints(hintList.ToArray()).ToArray();
AutofillValue autofillValue = viewNode.AutofillValue;
if (autofillValue != null)
{
if (autofillValue.IsList)
{
string[] autofillOptions = viewNode.GetAutofillOptions();
int index = autofillValue.ListValue;
if (autofillOptions != null && autofillOptions.Length > 0)
{
TextValue = autofillOptions[index];
}
}
else if (autofillValue.IsDate)
{
DateValue = autofillValue.DateValue;
}
else if (autofillValue.IsText)
{
TextValue = autofillValue.TextValue;
}
}
}
public bool IsNull()
{
return TextValue == null && DateValue == null && ToggleValue == null;
}
public override bool Equals(object obj)
{
if (this == obj) return true;
if (obj == null || GetType() != obj.GetType()) return false;
FilledAutofillField that = (FilledAutofillField)obj;
if (!TextValue?.Equals(that.TextValue) ?? that.TextValue != null)
return false;
if (DateValue != null ? !DateValue.Equals(that.DateValue) : that.DateValue != null)
return false;
return ToggleValue != null ? ToggleValue.Equals(that.ToggleValue) : that.ToggleValue == null;
}
public override int GetHashCode()
{
unchecked
{
var result = TextValue != null ? TextValue.GetHashCode() : 0;
result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
return result;
}
}
}
}

View File

@@ -7,164 +7,5 @@ using Android.Views.Autofill;
namespace keepass2android.services.AutofillBase.model
{
/// <summary>
/// FilledAutofillFieldCollection is the model that holds all of the data on a client app's page,
/// plus the dataset name associated with it.
/// </summary>
public class FilledAutofillFieldCollection
{
public Dictionary<string, FilledAutofillField> HintMap { get; }
public string DatasetName { get; set; }
public FilledAutofillFieldCollection(Dictionary<string, FilledAutofillField> hintMap, string datasetName = "")
{
//recreate hint map making sure we compare case insensitive
HintMap = BuildHintMap();
foreach (var p in hintMap)
HintMap.Add(p.Key, p.Value);
DatasetName = datasetName;
}
public FilledAutofillFieldCollection() : this(BuildHintMap())
{}
private static Dictionary<string, FilledAutofillField> BuildHintMap()
{
return new Dictionary<string, FilledAutofillField>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Adds a filledAutofillField to the collection, indexed by all of its hints.
/// </summary>
/// <returns>The add.</returns>
/// <param name="filledAutofillField">Filled autofill field.</param>
public void Add(FilledAutofillField filledAutofillField)
{
foreach (string hint in filledAutofillField.AutofillHints)
{
if (AutofillHintsHelper.IsSupportedHint(hint))
{
HintMap.TryAdd(hint, filledAutofillField);
}
else
{
CommonUtil.loge($"Invalid hint: {hint}");
}
}
}
/// <summary>
/// Populates a Dataset.Builder with appropriate values for each AutofillId
/// in a AutofillFieldMetadataCollection.
///
/// In other words, it constructs an autofill Dataset.Builder
/// by applying saved values (from this FilledAutofillFieldCollection)
/// to Views specified in a AutofillFieldMetadataCollection, which represents the current
/// page the user is on.
/// </summary>
/// <returns><c>true</c>, if to fields was applyed, <c>false</c> otherwise.</returns>
/// <param name="autofillFieldMetadataCollection">Autofill field metadata collection.</param>
/// <param name="datasetBuilder">Dataset builder.</param>
public bool ApplyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection, Dataset.Builder datasetBuilder)
{
bool setValueAtLeastOnce = false;
foreach (string hint in autofillFieldMetadataCollection.AllAutofillCanonicalHints)
{
foreach (AutofillFieldMetadata autofillFieldMetadata in autofillFieldMetadataCollection.GetFieldsForHint(hint))
{
FilledAutofillField filledAutofillField;
if (!HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null))
{
continue;
}
var autofillId = autofillFieldMetadata.AutofillId;
var autofillType = autofillFieldMetadata.AutofillType;
switch (autofillType)
{
case AutofillType.List:
var listValue = autofillFieldMetadata.GetAutofillOptionIndex(filledAutofillField.TextValue);
if (listValue != -1)
{
datasetBuilder.SetValue(autofillId, AutofillValue.ForList(listValue));
setValueAtLeastOnce = true;
}
break;
case AutofillType.Date:
var dateValue = filledAutofillField.DateValue;
datasetBuilder.SetValue(autofillId, AutofillValue.ForDate((long)dateValue));
setValueAtLeastOnce = true;
break;
case AutofillType.Text:
var textValue = filledAutofillField.TextValue;
if (textValue != null)
{
datasetBuilder.SetValue(autofillId, AutofillValue.ForText(textValue));
setValueAtLeastOnce = true;
}
break;
case AutofillType.Toggle:
var toggleValue = filledAutofillField.ToggleValue;
if (toggleValue != null)
{
datasetBuilder.SetValue(autofillId, AutofillValue.ForToggle(toggleValue.Value));
setValueAtLeastOnce = true;
}
break;
default:
Log.Warn(CommonUtil.Tag, "Invalid autofill type - " + autofillType);
break;
}
}
}
/*
if (!setValueAtLeastOnce)
{
Kp2aLog.Log("No value set. Hint keys : " + string.Join(",", HintMap.Keys));
foreach (string hint in autofillFieldMetadataCollection.AllAutofillCanonicalHints)
{
Kp2aLog.Log("No value set. Hint = " + hint);
foreach (AutofillFieldMetadata autofillFieldMetadata in autofillFieldMetadataCollection
.GetFieldsForHint(hint))
{
Kp2aLog.Log("No value set. fieldForHint = " + autofillFieldMetadata.AutofillId.ToString());
FilledAutofillField filledAutofillField;
if (!HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null))
{
Kp2aLog.Log("No value set. Hint map does not contain value, " +
(filledAutofillField == null));
continue;
}
Kp2aLog.Log("autofill type=" + autofillFieldMetadata.AutofillType);
}
}
}*/
return setValueAtLeastOnce;
}
/// <summary>
/// Takes in a list of autofill hints (`autofillHints`), usually associated with a View or set of
/// Views. Returns whether any of the filled fields on the page have at least 1 of these
/// `autofillHint`s.
/// </summary>
/// <returns><c>true</c>, if with hints was helpsed, <c>false</c> otherwise.</returns>
/// <param name="autofillHints">Autofill hints.</param>
public bool HelpsWithHints(List<string> autofillHints)
{
for (int i = 0; i < autofillHints.Count; i++)
{
var autofillHint = autofillHints[i];
if (HintMap.ContainsKey(autofillHint) && !HintMap[autofillHint].IsNull())
{
return true;
}
}
return false;
}
}
}

View File

@@ -2,126 +2,5 @@
namespace keepass2android.services.AutofillBase.model
{
public class W3cHints
{
// Supported W3C autofill tokens (https://html.spec.whatwg.org/multipage/forms.html#autofill)
public const string HONORIFIC_PREFIX = "honorific-prefix";
public const string NAME = "name";
public const string GIVEN_NAME = "given-name";
public const string ADDITIONAL_NAME = "additional-name";
public const string FAMILY_NAME = "family-name";
public const string HONORIFIC_SUFFIX = "honorific-suffix";
public const string USERNAME = "username";
public const string NEW_PASSWORD = "new-password";
public const string CURRENT_PASSWORD = "current-password";
public const string ORGANIZATION_TITLE = "organization-title";
public const string ORGANIZATION = "organization";
public const string STREET_ADDRESS = "street-address";
public const string ADDRESS_LINE1 = "address-line1";
public const string ADDRESS_LINE2 = "address-line2";
public const string ADDRESS_LINE3 = "address-line3";
public const string ADDRESS_LEVEL4 = "address-level4";
public const string ADDRESS_LEVEL3 = "address-level3";
public const string ADDRESS_LEVEL2 = "address-level2";
public const string ADDRESS_LEVEL1 = "address-level1";
public const string COUNTRY = "country";
public const string COUNTRY_NAME = "country-name";
public const string POSTAL_CODE = "postal-code";
public const string CC_NAME = "cc-name";
public const string CC_GIVEN_NAME = "cc-given-name";
public const string CC_ADDITIONAL_NAME = "cc-additional-name";
public const string CC_FAMILY_NAME = "cc-family-name";
public const string CC_NUMBER = "cc-number";
public const string CC_EXPIRATION = "cc-exp";
public const string CC_EXPIRATION_MONTH = "cc-exp-month";
public const string CC_EXPIRATION_YEAR = "cc-exp-year";
public const string CC_CSC = "cc-csc";
public const string CC_TYPE = "cc-type";
public const string TRANSACTION_CURRENCY = "transaction-currency";
public const string TRANSACTION_AMOUNT = "transaction-amount";
public const string LANGUAGE = "language";
public const string BDAY = "bday";
public const string BDAY_DAY = "bday-day";
public const string BDAY_MONTH = "bday-month";
public const string BDAY_YEAR = "bday-year";
public const string SEX = "sex";
public const string URL = "url";
public const string PHOTO = "photo";
// Optional W3C prefixes
public const string PREFIX_SECTION = "section-";
public const string SHIPPING = "shipping";
public const string BILLING = "billing";
// W3C prefixes below...
public const string PREFIX_HOME = "home";
public const string PREFIX_WORK = "work";
public const string PREFIX_FAX = "fax";
public const string PREFIX_PAGER = "pager";
// ... require those suffix
public const string TEL = "tel";
public const string TEL_COUNTRY_CODE = "tel-country-code";
public const string TEL_NATIONAL = "tel-national";
public const string TEL_AREA_CODE = "tel-area-code";
public const string TEL_LOCAL = "tel-local";
public const string TEL_LOCAL_PREFIX = "tel-local-prefix";
public const string TEL_LOCAL_SUFFIX = "tel-local-suffix";
public const string TEL_EXTENSION = "tel_extension";
public const string EMAIL = "email";
public const string IMPP = "impp";
private W3cHints()
{
}
public static bool isW3cSectionPrefix(string hint)
{
return hint.ToLower().StartsWith(W3cHints.PREFIX_SECTION);
}
public static bool isW3cAddressType(string hint)
{
switch (hint.ToLower())
{
case W3cHints.SHIPPING:
case W3cHints.BILLING:
return true;
}
return false;
}
public static bool isW3cTypePrefix(string hint)
{
switch (hint.ToLower())
{
case W3cHints.PREFIX_WORK:
case W3cHints.PREFIX_FAX:
case W3cHints.PREFIX_HOME:
case W3cHints.PREFIX_PAGER:
return true;
}
return false;
}
public static bool isW3cTypeHint(string hint)
{
switch (hint.ToLower())
{
case W3cHints.TEL:
case W3cHints.TEL_COUNTRY_CODE:
case W3cHints.TEL_NATIONAL:
case W3cHints.TEL_AREA_CODE:
case W3cHints.TEL_LOCAL:
case W3cHints.TEL_LOCAL_PREFIX:
case W3cHints.TEL_LOCAL_SUFFIX:
case W3cHints.TEL_EXTENSION:
case W3cHints.EMAIL:
case W3cHints.IMPP:
return true;
}
Log.Warn(CommonUtil.Tag, "Invalid W3C type hint: " + hint);
return false;
}
}
}

View File

@@ -241,6 +241,7 @@ namespace keepass2android
.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis())
.SetTicker(entryName + ": " + desc)
.SetVisibility((int)Android.App.NotificationVisibility.Secret)
.SetAutoCancel(true)
.SetContentIntent(pending);
if (entryIcon != null)
builder.SetLargeIcon(entryIcon);
@@ -951,7 +952,9 @@ namespace keepass2android
{
CopyToClipboardService.CopyValueToClipboardWithTimeout(context, username, false);
}
context.SendBroadcast(new Intent(Intent.ActionCloseSystemDialogs)); //close notification drawer
CloseNotificationDrawer(context);
}
else if (action.Equals(Intents.CopyPassword))
{
@@ -960,7 +963,7 @@ namespace keepass2android
{
CopyToClipboardService.CopyValueToClipboardWithTimeout(context, password, true);
}
context.SendBroadcast(new Intent(Intent.ActionCloseSystemDialogs)); //close notification drawer
CloseNotificationDrawer(context);
}
else if (action.Equals(Intents.CopyTotp))
{
@@ -969,7 +972,7 @@ namespace keepass2android
{
CopyToClipboardService.CopyValueToClipboardWithTimeout(context, totp, true);
}
context.SendBroadcast(new Intent(Intent.ActionCloseSystemDialogs)); //close notification drawer
CloseNotificationDrawer(context);
}
else if (action.Equals(Intents.CheckKeyboard))
{
@@ -977,6 +980,11 @@ namespace keepass2android
}
}
private static void CloseNotificationDrawer(Context context)
{
if ((int)Build.VERSION.SdkInt < 31) //sending this intent is no longer allowed since Android 31
context.SendBroadcast(new Intent(Intent.ActionCloseSystemDialogs)); //close notification drawer
}
};
}

View File

@@ -15,6 +15,7 @@ using keepass2android.services.AutofillBase.model;
using Keepass2android.Pluginsdk;
using KeePassLib;
using KeePassLib.Utility;
using Kp2aAutofillParser;
namespace keepass2android.services.Kp2aAutofill
{
@@ -41,7 +42,7 @@ namespace keepass2android.services.Kp2aAutofill
protected override Result ExpectedActivityResult => KeePass.ExitCloseAfterTaskComplete;
protected override FilledAutofillFieldCollection GetDataset()
protected override FilledAutofillFieldCollection<ViewNodeInputField> GetDataset()
{
if (App.Kp2a.CurrentDb==null || (App.Kp2a.QuickLocked))
return null;
@@ -50,18 +51,18 @@ namespace keepass2android.services.Kp2aAutofill
return GetFilledAutofillFieldCollectionFromEntry(entryOutput, this);
}
public static FilledAutofillFieldCollection GetFilledAutofillFieldCollectionFromEntry(PwEntryOutput pwEntryOutput, Context context)
public static FilledAutofillFieldCollection<ViewNodeInputField> GetFilledAutofillFieldCollectionFromEntry(PwEntryOutput pwEntryOutput, Context context)
{
if (pwEntryOutput == null)
return null;
FilledAutofillFieldCollection fieldCollection = new FilledAutofillFieldCollection();
FilledAutofillFieldCollection<ViewNodeInputField> fieldCollection = new FilledAutofillFieldCollection<ViewNodeInputField>();
var pwEntry = pwEntryOutput.Entry;
foreach (string key in pwEntryOutput.OutputStrings.GetKeys())
{
FilledAutofillField field =
new FilledAutofillField
FilledAutofillField<ViewNodeInputField> field =
new FilledAutofillField<ViewNodeInputField>
{
AutofillHints = GetCanonicalHintsFromKp2aField(key).ToArray(),
TextValue = pwEntryOutput.OutputStrings.ReadSafe(key)
@@ -72,8 +73,8 @@ namespace keepass2android.services.Kp2aAutofill
if (IsCreditCard(pwEntry, context) && pwEntry.Expires)
{
DateTime expTime = pwEntry.ExpiryTime;
FilledAutofillField field =
new FilledAutofillField
FilledAutofillField<ViewNodeInputField> field =
new FilledAutofillField<ViewNodeInputField>
{
AutofillHints = new[] {View.AutofillHintCreditCardExpirationDate},
DateValue = (long) (1000 * TimeUtil.SerializeUnix(expTime))
@@ -81,7 +82,7 @@ namespace keepass2android.services.Kp2aAutofill
fieldCollection.Add(field);
field =
new FilledAutofillField
new FilledAutofillField<ViewNodeInputField>
{
AutofillHints = new[] {View.AutofillHintCreditCardExpirationDay},
TextValue = expTime.Day.ToString()
@@ -89,7 +90,7 @@ namespace keepass2android.services.Kp2aAutofill
fieldCollection.Add(field);
field =
new FilledAutofillField
new FilledAutofillField<ViewNodeInputField>
{
AutofillHints = new[] {View.AutofillHintCreditCardExpirationMonth},
TextValue = expTime.Month.ToString()
@@ -97,7 +98,7 @@ namespace keepass2android.services.Kp2aAutofill
fieldCollection.Add(field);
field =
new FilledAutofillField
new FilledAutofillField<ViewNodeInputField>
{
AutofillHints = new[] {View.AutofillHintCreditCardExpirationYear},
TextValue = expTime.Year.ToString()

View File

@@ -12,6 +12,7 @@ using Keepass2android.Pluginsdk;
using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Utility;
using Kp2aAutofillParser;
using Org.Json;
using AutofillServiceBase = keepass2android.services.AutofillBase.AutofillServiceBase;
@@ -33,10 +34,10 @@ namespace keepass2android.services
{
}
protected override List<FilledAutofillFieldCollection> GetSuggestedEntries(string query)
protected override List<FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query)
{
if (!App.Kp2a.DatabaseIsUnlocked)
return new List<FilledAutofillFieldCollection>();
return new List<FilledAutofillFieldCollection<ViewNodeInputField>>();
var foundEntries = (ShareUrlResults.GetSearchResultsForUrl(query)?.Entries ?? new PwObjectList<PwEntry>())
.Select(e => new PwEntryOutput(e, App.Kp2a.FindDatabaseForElement(e)))
.ToList();

View File

@@ -0,0 +1,956 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Newtonsoft.Json;
using Formatting = System.Xml.Formatting;
namespace Kp2aAutofillParser
{
public class W3cHints
{
// Supported W3C autofill tokens (https://html.spec.whatwg.org/multipage/forms.html#autofill)
public const string HONORIFIC_PREFIX = "honorific-prefix";
public const string NAME = "name";
public const string GIVEN_NAME = "given-name";
public const string ADDITIONAL_NAME = "additional-name";
public const string FAMILY_NAME = "family-name";
public const string HONORIFIC_SUFFIX = "honorific-suffix";
public const string USERNAME = "username";
public const string NEW_PASSWORD = "new-password";
public const string CURRENT_PASSWORD = "current-password";
public const string ORGANIZATION_TITLE = "organization-title";
public const string ORGANIZATION = "organization";
public const string STREET_ADDRESS = "street-address";
public const string ADDRESS_LINE1 = "address-line1";
public const string ADDRESS_LINE2 = "address-line2";
public const string ADDRESS_LINE3 = "address-line3";
public const string ADDRESS_LEVEL4 = "address-level4";
public const string ADDRESS_LEVEL3 = "address-level3";
public const string ADDRESS_LEVEL2 = "address-level2";
public const string ADDRESS_LEVEL1 = "address-level1";
public const string COUNTRY = "country";
public const string COUNTRY_NAME = "country-name";
public const string POSTAL_CODE = "postal-code";
public const string CC_NAME = "cc-name";
public const string CC_GIVEN_NAME = "cc-given-name";
public const string CC_ADDITIONAL_NAME = "cc-additional-name";
public const string CC_FAMILY_NAME = "cc-family-name";
public const string CC_NUMBER = "cc-number";
public const string CC_EXPIRATION = "cc-exp";
public const string CC_EXPIRATION_MONTH = "cc-exp-month";
public const string CC_EXPIRATION_YEAR = "cc-exp-year";
public const string CC_CSC = "cc-csc";
public const string CC_TYPE = "cc-type";
public const string TRANSACTION_CURRENCY = "transaction-currency";
public const string TRANSACTION_AMOUNT = "transaction-amount";
public const string LANGUAGE = "language";
public const string BDAY = "bday";
public const string BDAY_DAY = "bday-day";
public const string BDAY_MONTH = "bday-month";
public const string BDAY_YEAR = "bday-year";
public const string SEX = "sex";
public const string URL = "url";
public const string PHOTO = "photo";
// Optional W3C prefixes
public const string PREFIX_SECTION = "section-";
public const string SHIPPING = "shipping";
public const string BILLING = "billing";
// W3C prefixes below...
public const string PREFIX_HOME = "home";
public const string PREFIX_WORK = "work";
public const string PREFIX_FAX = "fax";
public const string PREFIX_PAGER = "pager";
// ... require those suffix
public const string TEL = "tel";
public const string TEL_COUNTRY_CODE = "tel-country-code";
public const string TEL_NATIONAL = "tel-national";
public const string TEL_AREA_CODE = "tel-area-code";
public const string TEL_LOCAL = "tel-local";
public const string TEL_LOCAL_PREFIX = "tel-local-prefix";
public const string TEL_LOCAL_SUFFIX = "tel-local-suffix";
public const string TEL_EXTENSION = "tel_extension";
public const string EMAIL = "email";
public const string IMPP = "impp";
private W3cHints()
{
}
public static bool isW3cSectionPrefix(string hint)
{
return hint.ToLower().StartsWith(W3cHints.PREFIX_SECTION);
}
public static bool isW3cAddressType(string hint)
{
switch (hint.ToLower())
{
case W3cHints.SHIPPING:
case W3cHints.BILLING:
return true;
}
return false;
}
public static bool isW3cTypePrefix(string hint)
{
switch (hint.ToLower())
{
case W3cHints.PREFIX_WORK:
case W3cHints.PREFIX_FAX:
case W3cHints.PREFIX_HOME:
case W3cHints.PREFIX_PAGER:
return true;
}
return false;
}
public static bool isW3cTypeHint(string hint)
{
switch (hint.ToLower())
{
case W3cHints.TEL:
case W3cHints.TEL_COUNTRY_CODE:
case W3cHints.TEL_NATIONAL:
case W3cHints.TEL_AREA_CODE:
case W3cHints.TEL_LOCAL:
case W3cHints.TEL_LOCAL_PREFIX:
case W3cHints.TEL_LOCAL_SUFFIX:
case W3cHints.TEL_EXTENSION:
case W3cHints.EMAIL:
case W3cHints.IMPP:
return true;
}
return false;
}
}
/// <summary>
/// FilledAutofillFieldCollection is the model that holds all of the data on a client app's page,
/// plus the dataset name associated with it.
/// </summary>
public class FilledAutofillFieldCollection<FieldT> where FieldT:InputField
{
public Dictionary<string, FilledAutofillField<FieldT>> HintMap { get; }
public string DatasetName { get; set; }
public FilledAutofillFieldCollection(Dictionary<string, FilledAutofillField<FieldT>> hintMap, string datasetName = "")
{
//recreate hint map making sure we compare case insensitive
HintMap = BuildHintMap();
foreach (var p in hintMap)
HintMap.Add(p.Key, p.Value);
DatasetName = datasetName;
}
public FilledAutofillFieldCollection() : this(BuildHintMap())
{ }
private static Dictionary<string, FilledAutofillField<FieldT>> BuildHintMap()
{
return new Dictionary<string, FilledAutofillField<FieldT>>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Adds a filledAutofillField to the collection, indexed by all of its hints.
/// </summary>
/// <returns>The add.</returns>
/// <param name="filledAutofillField">Filled autofill field.</param>
public void Add(FilledAutofillField<FieldT> filledAutofillField)
{
foreach (string hint in filledAutofillField.AutofillHints)
{
if (AutofillHintsHelper.IsSupportedHint(hint))
{
HintMap.TryAdd(hint, filledAutofillField);
}
}
}
/// <summary>
/// Takes in a list of autofill hints (`autofillHints`), usually associated with a View or set of
/// Views. Returns whether any of the filled fields on the page have at least 1 of these
/// `autofillHint`s.
/// </summary>
/// <returns><c>true</c>, if with hints was helpsed, <c>false</c> otherwise.</returns>
/// <param name="autofillHints">Autofill hints.</param>
public bool HelpsWithHints(List<string> autofillHints)
{
for (int i = 0; i < autofillHints.Count; i++)
{
var autofillHint = autofillHints[i];
if (HintMap.ContainsKey(autofillHint) && !HintMap[autofillHint].IsNull())
{
return true;
}
}
return false;
}
}
public class AutofillHintsHelper
{
public const string AutofillHint2faAppOtp = "2faAppOTPCode";
public const string AutofillHintBirthDateDay = "birthDateDay";
public const string AutofillHintBirthDateFull = "birthDateFull";
public const string AutofillHintBirthDateMonth = "birthDateMonth";
public const string AutofillHintBirthDateYear = "birthDateYear";
public const string AutofillHintCreditCardExpirationDate = "creditCardExpirationDate";
public const string AutofillHintCreditCardExpirationDay = "creditCardExpirationDay";
public const string AutofillHintCreditCardExpirationMonth = "creditCardExpirationMonth";
public const string AutofillHintCreditCardExpirationYear = "creditCardExpirationYear";
public const string AutofillHintCreditCardNumber = "creditCardNumber";
public const string AutofillHintCreditCardSecurityCode = "creditCardSecurityCode";
public const string AutofillHintEmailAddress = "emailAddress";
public const string AutofillHintEmailOtp = "emailOTPCode";
public const string AutofillHintGender = "gender";
public const string AutofillHintName = "name";
public const string AutofillHintNewPassword = "newPassword";
public const string AutofillHintNewUsername = "newUsername";
public const string AutofillHintNotApplicable = "notApplicable";
public const string AutofillHintPassword = "password";
public const string AutofillHintPersonName = "personName";
public const string AutofillHintPersonNameFAMILY = "personFamilyName";
public const string AutofillHintPersonNameGIVEN = "personGivenName";
public const string AutofillHintPersonNameMIDDLE = "personMiddleName";
public const string AutofillHintPersonNameMIDDLE_INITIAL = "personMiddleInitial";
public const string AutofillHintPersonNamePREFIX = "personNamePrefix";
public const string AutofillHintPersonNameSUFFIX = "personNameSuffix";
public const string AutofillHintPhone = "phone";
public const string AutofillHintPhoneContryCode = "phoneCountryCode";
public const string AutofillHintPostalAddressAPT_NUMBER = "aptNumber";
public const string AutofillHintPostalAddressCOUNTRY = "addressCountry";
public const string AutofillHintPostalAddressDEPENDENT_LOCALITY = "dependentLocality";
public const string AutofillHintPostalAddressEXTENDED_ADDRESS = "extendedAddress";
public const string AutofillHintPostalAddressEXTENDED_POSTAL_CODE = "extendedPostalCode";
public const string AutofillHintPostalAddressLOCALITY = "addressLocality";
public const string AutofillHintPostalAddressREGION = "addressRegion";
public const string AutofillHintPostalAddressSTREET_ADDRESS = "streetAddress";
public const string AutofillHintPostalCode = "postalCode";
public const string AutofillHintPromoCode = "promoCode";
public const string AutofillHintSMS_OTP = "smsOTPCode";
public const string AutofillHintUPI_VPA = "upiVirtualPaymentAddress";
public const string AutofillHintUsername = "username";
public const string AutofillHintWifiPassword = "wifiPassword";
public const string AutofillHintPhoneNational = "phoneNational";
public const string AutofillHintPhoneNumber = "phoneNumber";
public const string AutofillHintPhoneNumberDevice = "phoneNumberDevice";
public const string AutofillHintPostalAddress = "postalAddress";
private static readonly HashSet<string> _allSupportedHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
AutofillHintCreditCardExpirationDate,
AutofillHintCreditCardExpirationDay,
AutofillHintCreditCardExpirationMonth,
AutofillHintCreditCardExpirationYear,
AutofillHintCreditCardNumber,
AutofillHintCreditCardSecurityCode,
AutofillHintEmailAddress,
AutofillHintPhone,
AutofillHintName,
AutofillHintPassword,
AutofillHintPostalAddress,
AutofillHintPostalCode,
AutofillHintUsername,
W3cHints.HONORIFIC_PREFIX,
W3cHints.NAME,
W3cHints.GIVEN_NAME,
W3cHints.ADDITIONAL_NAME,
W3cHints.FAMILY_NAME,
W3cHints.HONORIFIC_SUFFIX,
W3cHints.USERNAME,
W3cHints.NEW_PASSWORD,
W3cHints.CURRENT_PASSWORD,
W3cHints.ORGANIZATION_TITLE,
W3cHints.ORGANIZATION,
W3cHints.STREET_ADDRESS,
W3cHints.ADDRESS_LINE1,
W3cHints.ADDRESS_LINE2,
W3cHints.ADDRESS_LINE3,
W3cHints.ADDRESS_LEVEL4,
W3cHints.ADDRESS_LEVEL3,
W3cHints.ADDRESS_LEVEL2,
W3cHints.ADDRESS_LEVEL1,
W3cHints.COUNTRY,
W3cHints.COUNTRY_NAME,
W3cHints.POSTAL_CODE,
W3cHints.CC_NAME,
W3cHints.CC_GIVEN_NAME,
W3cHints.CC_ADDITIONAL_NAME,
W3cHints.CC_FAMILY_NAME,
W3cHints.CC_NUMBER,
W3cHints.CC_EXPIRATION,
W3cHints.CC_EXPIRATION_MONTH,
W3cHints.CC_EXPIRATION_YEAR,
W3cHints.CC_CSC,
W3cHints.CC_TYPE,
W3cHints.TRANSACTION_CURRENCY,
W3cHints.TRANSACTION_AMOUNT,
W3cHints.LANGUAGE,
W3cHints.BDAY,
W3cHints.BDAY_DAY,
W3cHints.BDAY_MONTH,
W3cHints.BDAY_YEAR,
W3cHints.SEX,
W3cHints.URL,
W3cHints.PHOTO,
W3cHints.TEL,
W3cHints.TEL_COUNTRY_CODE,
W3cHints.TEL_NATIONAL,
W3cHints.TEL_AREA_CODE,
W3cHints.TEL_LOCAL,
W3cHints.TEL_LOCAL_PREFIX,
W3cHints.TEL_LOCAL_SUFFIX,
W3cHints.TEL_EXTENSION,
W3cHints.EMAIL,
W3cHints.IMPP,
};
private static readonly List<HashSet<string>> partitionsOfCanonicalHints = new List<HashSet<string>>()
{
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
AutofillHintEmailAddress,
AutofillHintPhone,
AutofillHintName,
AutofillHintPassword,
AutofillHintUsername,
W3cHints.HONORIFIC_PREFIX,
W3cHints.NAME,
W3cHints.GIVEN_NAME,
W3cHints.ADDITIONAL_NAME,
W3cHints.FAMILY_NAME,
W3cHints.HONORIFIC_SUFFIX,
W3cHints.ORGANIZATION_TITLE,
W3cHints.ORGANIZATION,
W3cHints.LANGUAGE,
W3cHints.BDAY,
W3cHints.BDAY_DAY,
W3cHints.BDAY_MONTH,
W3cHints.BDAY_YEAR,
W3cHints.SEX,
W3cHints.URL,
W3cHints.PHOTO,
W3cHints.TEL,
W3cHints.TEL_COUNTRY_CODE,
W3cHints.TEL_NATIONAL,
W3cHints.TEL_AREA_CODE,
W3cHints.TEL_LOCAL,
W3cHints.TEL_LOCAL_PREFIX,
W3cHints.TEL_LOCAL_SUFFIX,
W3cHints.TEL_EXTENSION,
W3cHints.IMPP,
},
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
AutofillHintPostalAddress,
AutofillHintPostalCode,
W3cHints.STREET_ADDRESS,
W3cHints.ADDRESS_LINE1,
W3cHints.ADDRESS_LINE2,
W3cHints.ADDRESS_LINE3,
W3cHints.ADDRESS_LEVEL4,
W3cHints.ADDRESS_LEVEL3,
W3cHints.ADDRESS_LEVEL2,
W3cHints.ADDRESS_LEVEL1,
W3cHints.COUNTRY,
W3cHints.COUNTRY_NAME
},
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
AutofillHintCreditCardExpirationDate,
AutofillHintCreditCardExpirationDay,
AutofillHintCreditCardExpirationMonth,
AutofillHintCreditCardExpirationYear,
AutofillHintCreditCardNumber,
AutofillHintCreditCardSecurityCode,
W3cHints.CC_NAME,
W3cHints.CC_GIVEN_NAME,
W3cHints.CC_ADDITIONAL_NAME,
W3cHints.CC_FAMILY_NAME,
W3cHints.CC_TYPE,
W3cHints.TRANSACTION_CURRENCY,
W3cHints.TRANSACTION_AMOUNT,
},
};
private static readonly Dictionary<string, string> hintToCanonicalReplacement = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{W3cHints.EMAIL, AutofillHintEmailAddress},
{W3cHints.USERNAME, AutofillHintUsername},
{W3cHints.CURRENT_PASSWORD, AutofillHintPassword},
{W3cHints.NEW_PASSWORD, AutofillHintPassword},
{W3cHints.CC_EXPIRATION_MONTH, AutofillHintCreditCardExpirationMonth },
{W3cHints.CC_EXPIRATION_YEAR, AutofillHintCreditCardExpirationYear },
{W3cHints.CC_EXPIRATION, AutofillHintCreditCardExpirationDate },
{W3cHints.CC_NUMBER, AutofillHintCreditCardNumber },
{W3cHints.CC_CSC, AutofillHintCreditCardSecurityCode },
{W3cHints.POSTAL_CODE, AutofillHintPostalCode },
};
public static bool IsSupportedHint(string hint)
{
return _allSupportedHints.Contains(hint);
}
public static string[] FilterForSupportedHints(string[] hints)
{
if (hints == null)
return Array.Empty<string>();
var filteredHints = new string[hints.Length];
int i = 0;
foreach (var hint in hints)
{
if (IsSupportedHint(hint))
{
filteredHints[i++] = hint;
}
}
var finalFilteredHints = new string[i];
Array.Copy(filteredHints, 0, finalFilteredHints, 0, i);
return finalFilteredHints;
}
/// <summary>
/// transforms hints by replacing some W3cHints by their Android counterparts and transforming everything to lowercase
/// </summary>
public static List<string> ConvertToCanonicalHints(string[] supportedHints)
{
List<string> result = new List<string>();
foreach (string hint in supportedHints)
{
string canonicalHint;
if (!hintToCanonicalReplacement.TryGetValue(hint, out canonicalHint))
canonicalHint = hint;
result.Add(canonicalHint.ToLower());
}
return result;
}
public static int GetPartitionIndex(string hint)
{
for (int i = 0; i < partitionsOfCanonicalHints.Count; i++)
{
if (partitionsOfCanonicalHints[i].Contains(hint))
{
return i;
}
}
return -1;
}
public static FilledAutofillFieldCollection<FieldT> FilterForPartition<FieldT>(FilledAutofillFieldCollection<FieldT> autofillFields, int partitionIndex) where FieldT: InputField
{
FilledAutofillFieldCollection<FieldT> filteredCollection =
new FilledAutofillFieldCollection<FieldT> { DatasetName = autofillFields.DatasetName };
if (partitionIndex == -1)
return filteredCollection;
foreach (var field in autofillFields.HintMap.Values.Distinct())
{
foreach (var hint in field.AutofillHints)
{
if (GetPartitionIndex(hint) == partitionIndex)
{
filteredCollection.Add(field);
break;
}
}
}
return filteredCollection;
}
public static FilledAutofillFieldCollection<FieldT> FilterForPartition<FieldT>(FilledAutofillFieldCollection<FieldT> filledAutofillFieldCollection, List<string> autofillFieldsFocusedAutofillCanonicalHints) where FieldT: InputField
{
//only apply partition data if we have FocusedAutofillCanonicalHints. This may be empty on buggy Firefox.
if (autofillFieldsFocusedAutofillCanonicalHints.Any())
{
int partitionIndex = AutofillHintsHelper.GetPartitionIndex(autofillFieldsFocusedAutofillCanonicalHints.FirstOrDefault());
return AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, partitionIndex);
}
return filledAutofillFieldCollection;
}
}
/// <summary>
/// This enum represents the Android.Text.InputTypes values. For testability, this is duplicated here.
/// </summary>
public enum InputTypes
{
ClassDatetime = 4,
ClassNumber = 2,
ClassPhone = 3,
ClassText = 1,
DatetimeVariationDate = 16,
DatetimeVariationNormal = 0,
DatetimeVariationTime = 32,
MaskClass = 15,
MaskFlags = 16773120,
MaskVariation = 4080,
Null = 0,
NumberFlagDecimal = 8192,
NumberFlagSigned = 4096,
NumberVariationNormal = 0,
NumberVariationPassword = 16,
TextFlagAutoComplete = 65536,
TextFlagAutoCorrect = 32768,
TextFlagCapCharacters = 4096,
TextFlagCapSentences = 16384,
TextFlagCapWords = 8192,
TextFlagEnableTextConversionSuggestions = 1048576,
TextFlagImeMultiLine = 262144,
TextFlagMultiLine = 131072,
TextFlagNoSuggestions = 524288,
TextVariationEmailAddress = 32,
TextVariationEmailSubject = 48,
TextVariationFilter = 176,
TextVariationLongMessage = 80,
TextVariationNormal = 0,
TextVariationPassword = 128,
TextVariationPersonName = 96,
TextVariationPhonetic = 192,
TextVariationPostalAddress = 112,
TextVariationShortMessage = 64,
TextVariationUri = 16,
TextVariationVisiblePassword = 144,
TextVariationWebEditText = 160,
TextVariationWebEmailAddress = 208,
TextVariationWebPassword = 224
}
public interface IKp2aDigitalAssetLinksDataSource
{
bool IsTrustedApp(string packageName);
bool IsTrustedLink(string domain, string targetPackage);
bool IsEnabled();
}
class TimeUtil
{
private static DateTime? m_dtUnixRoot = null;
public static DateTime ConvertUnixTime(double dtUnix)
{
try
{
if (!m_dtUnixRoot.HasValue)
m_dtUnixRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0,
DateTimeKind.Utc)).ToLocalTime();
return m_dtUnixRoot.Value.AddSeconds(dtUnix);
}
catch (Exception) { Debug.Assert(false); }
return DateTime.UtcNow;
}
}
public class FilledAutofillField<FieldT> where FieldT : InputField
{
private string[] _autofillHints;
public string TextValue { get; set; }
public long? DateValue { get; set; }
public bool? ToggleValue { get; set; }
public string ValueToString()
{
if (DateValue != null)
{
return TimeUtil.ConvertUnixTime((long)DateValue / 1000.0).ToLongDateString();
}
if (ToggleValue != null)
return ToggleValue.ToString();
return TextValue;
}
/// <summary>
/// returns the autofill hints for the filled field. These are always lowercased for simpler string comparison.
/// </summary>
public string[] AutofillHints
{
get
{
return _autofillHints;
}
set
{
_autofillHints = value;
for (int i = 0; i < _autofillHints.Length; i++)
_autofillHints[i] = _autofillHints[i].ToLower();
}
}
public FilledAutofillField()
{ }
public FilledAutofillField(FieldT inputField)
: this(inputField, inputField.AutofillHints)
{
}
public FilledAutofillField(FieldT inputField, string[] hints)
{
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(hints);
List<string> hintList = new List<string>();
string nextHint = null;
for (int i = 0; i < rawHints.Length; i++)
{
string hint = rawHints[i];
if (i < rawHints.Length - 1)
{
nextHint = rawHints[i + 1];
}
// First convert the compound W3C autofill hints
if (W3cHints.isW3cSectionPrefix(hint) && i < rawHints.Length - 1)
{
hint = rawHints[++i];
if (i < rawHints.Length - 1)
{
nextHint = rawHints[i + 1];
}
}
if (W3cHints.isW3cTypePrefix(hint) && nextHint != null && W3cHints.isW3cTypeHint(nextHint))
{
hint = nextHint;
i++;
}
if (W3cHints.isW3cAddressType(hint) && nextHint != null)
{
hint = nextHint;
i++;
}
// Then check if the "actual" hint is supported.
if (AutofillHintsHelper.IsSupportedHint(hint))
{
hintList.Add(hint);
}
else
{
}
}
AutofillHints = AutofillHintsHelper.ConvertToCanonicalHints(hintList.ToArray()).ToArray();
}
public bool IsNull()
{
return TextValue == null && DateValue == null && ToggleValue == null;
}
public override bool Equals(object obj)
{
if (this == obj) return true;
if (obj == null || GetType() != obj.GetType()) return false;
FilledAutofillField<FieldT> that = (FilledAutofillField<FieldT>)obj;
if (!TextValue?.Equals(that.TextValue) ?? that.TextValue != null)
return false;
if (DateValue != null ? !DateValue.Equals(that.DateValue) : that.DateValue != null)
return false;
return ToggleValue != null ? ToggleValue.Equals(that.ToggleValue) : that.ToggleValue == null;
}
public override int GetHashCode()
{
unchecked
{
var result = TextValue != null ? TextValue.GetHashCode() : 0;
result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
return result;
}
}
}
/// <summary>
/// Base class for everything that is a input field which might (or might not) be autofilled.
/// For testability, this is independent from Android classes like ViewNode
/// </summary>
public abstract class InputField
{
public string IdEntry { get; set; }
public string Hint { get; set; }
public string ClassName { get; set; }
public string[] AutofillHints { get; set; }
public bool IsFocused { get; set; }
public InputTypes InputType { get; set; }
public string HtmlInfoTag { get; set; }
public string HtmlInfoTypeAttribute { get; set; }
}
public class AutofillView<TField> where TField : InputField
{
public List<TField> InputFields { get; set; } = new List<TField>();
public string PackageId { get; set; } = null;
public string WebDomain { get; set; } = null;
}
public interface ILogger
{
void Log(string x);
}
public class StructureParserBase<FieldT> where FieldT: InputField
{
private readonly ILogger _log;
private readonly IKp2aDigitalAssetLinksDataSource _digitalAssetLinksDataSource;
private readonly List<string> _autofillHintsForLogin = new List<string>
{
AutofillHintsHelper.AutofillHintPassword,
AutofillHintsHelper.AutofillHintUsername,
AutofillHintsHelper.AutofillHintEmailAddress
};
public string PackageId { get; set; }
public Dictionary<FieldT, string[]> FieldsMappedToHints = new Dictionary<FieldT, string[]>();
public StructureParserBase(ILogger logger, IKp2aDigitalAssetLinksDataSource digitalAssetLinksDataSource)
{
_log = logger;
_digitalAssetLinksDataSource = digitalAssetLinksDataSource;
}
public class AutofillTargetId
{
public string PackageName { get; set; }
public string PackageNameWithPseudoSchema
{
get { return AndroidAppScheme + PackageName; }
}
public const string AndroidAppScheme = "androidapp://";
public string WebDomain { get; set; }
/// <summary>
/// If PackageName and WebDomain are not compatible (by DAL or because PackageName is a trusted browser in which case we treat all domains as "compatible"
/// we need to issue a warning. If we would fill credentials for the package, a malicious website could try to get credentials for the app.
/// If we would fill credentials for the domain, a malicious app could get credentials for the domain.
/// </summary>
public bool IncompatiblePackageAndDomain { get; set; }
public string DomainOrPackage
{
get
{
return WebDomain ?? PackageNameWithPseudoSchema;
}
}
}
public AutofillTargetId ParseForFill(bool isManual, AutofillView<FieldT> autofillView)
{
return Parse(true, isManual, autofillView);
}
public AutofillTargetId ParseForSave(AutofillView<FieldT> autofillView)
{
return Parse(false, true, autofillView);
}
/// <summary>
/// Traverse AssistStructure and add ViewNode metadata to a flat list.
/// </summary>
/// <returns>The parse.</returns>
/// <param name="forFill">If set to <c>true</c> for fill.</param>
/// <param name="isManualRequest"></param>
protected virtual AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<FieldT> autofillView)
{
AutofillTargetId result = new AutofillTargetId()
{
PackageName = autofillView.PackageId,
WebDomain = autofillView.WebDomain
};
_log.Log("parsing autofillStructure...");
//TODO remove from production
_log.Log("will log the autofillStructure...");
string debugInfo = JsonConvert.SerializeObject(autofillView, Newtonsoft.Json.Formatting.Indented);
_log.Log("will log the autofillStructure... size is " + debugInfo.Length);
_log.Log("This is the autofillStructure: \n\n " + debugInfo);
//go through each input field and determine username/password fields.
//Depending on the target this can require more or less heuristics.
// * if there is a valid & supported autofill hint, we assume that all fields which should be filled do have an appropriate Autofill hint
// * if there is no such autofill hint, we use IsPassword to
HashSet<string> autofillHintsOfAllFields = autofillView.InputFields.Where(f => f.AutofillHints != null)
.SelectMany(f => f.AutofillHints).ToHashSet();
bool hasLoginAutofillHints = autofillHintsOfAllFields.Intersect(_autofillHintsForLogin).Any();
if (hasLoginAutofillHints)
{
foreach (var viewNode in autofillView.InputFields)
{
string[] viewHints = viewNode.AutofillHints;
if (viewHints == null)
continue;
if (viewHints.Intersect(_autofillHintsForLogin).Any())
{
FieldsMappedToHints.Add(viewNode, viewHints);
}
}
}
else
{
//determine password fields, first by type, then by hint:
List<FieldT> passwordFields = autofillView.InputFields.Where(f => IsEditText(f) && IsPassword(f)).ToList();
if (!passwordFields.Any())
{
passwordFields = autofillView.InputFields.Where(f => IsEditText(f) && HasPasswordHint(f)).ToList();
}
//determine username fields. Try by hint, if that fails use the one before the password
List<FieldT> usernameFields = autofillView.InputFields.Where(f => IsEditText(f) && HasUsernameHint(f)).ToList();
if (!usernameFields.Any())
{
foreach (var passwordField in passwordFields)
{
var lastInputBeforePassword = autofillView.InputFields
.TakeWhile(f => IsEditText(f) && f != passwordField && !passwordFields.Contains(f)).LastOrDefault();
if (lastInputBeforePassword != null)
usernameFields.Add(lastInputBeforePassword);
}
}
//for "heuristic determination" we demand that one of the filled fields is focused:
if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused))
{
foreach (var uf in usernameFields)
FieldsMappedToHints.Add(uf, new string[] { AutofillHintsHelper.AutofillHintUsername });
foreach (var pf in passwordFields)
FieldsMappedToHints.Add(pf, new string[] { AutofillHintsHelper.AutofillHintPassword });
}
}
if (!string.IsNullOrEmpty(autofillView.WebDomain) && _digitalAssetLinksDataSource.IsEnabled())
{
result.IncompatiblePackageAndDomain = !_digitalAssetLinksDataSource.IsTrustedLink(autofillView.WebDomain, result.PackageName);
if (result.IncompatiblePackageAndDomain)
{
_log.Log($"DAL verification failed for {result.PackageName}/{result.WebDomain}");
}
}
else
{
result.IncompatiblePackageAndDomain = false;
}
return result;
}
private bool IsEditText(FieldT f)
{
return (f.ClassName == "android.widget.EditText"
|| f.ClassName == "android.widget.AutoCompleteTextView"
|| f.HtmlInfoTag == "input");
}
private static readonly HashSet<string> _passwordHints = new HashSet<string> { "password", "passwort"
/*, "passwordAuto", "pswd"*/ };
private static bool HasPasswordHint(InputField f)
{
return IsAny(f.IdEntry, _passwordHints) ||
IsAny(f.Hint, _passwordHints);
}
private static readonly HashSet<string> _usernameHints = new HashSet<string> { "email", "e-mail", "username" };
private static bool HasUsernameHint(InputField f)
{
return IsAny(f.IdEntry, _usernameHints) ||
IsAny(f.Hint, _usernameHints);
}
private static bool IsAny(string value, IEnumerable<string> terms)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
var lowerValue = value.ToLowerInvariant();
return terms.Any(t => lowerValue == t);
}
private static bool IsInputTypeClass(InputTypes inputType, InputTypes inputTypeClass)
{
if (!InputTypes.MaskClass.HasFlag(inputTypeClass))
throw new Exception("invalid inputTypeClass");
return (((int)inputType) & (int)InputTypes.MaskClass) == (int)(inputTypeClass);
}
private static bool IsInputTypeVariation(InputTypes inputType, InputTypes inputTypeVariation)
{
if (!InputTypes.MaskVariation.HasFlag(inputTypeVariation))
throw new Exception("invalid inputTypeVariation");
return (((int)inputType) & (int)InputTypes.MaskVariation) == (int)(inputTypeVariation);
}
private static bool IsPassword(InputField f)
{
InputTypes inputType = f.InputType;
return
(!f.IdEntry?.ToLowerInvariant().Contains("search") ?? true) &&
(!f.Hint?.ToLowerInvariant().Contains("search") ?? true) &&
(
(IsInputTypeClass(inputType, InputTypes.ClassText)
&&
(
IsInputTypeVariation(inputType, InputTypes.TextVariationPassword)
|| IsInputTypeVariation(inputType, InputTypes.TextVariationVisiblePassword)
|| IsInputTypeVariation(inputType, InputTypes.TextVariationWebPassword)
)
)
|| (f.AutofillHints != null && f.AutofillHints.First() == "passwordAuto")
|| (f.HtmlInfoTypeAttribute == "password")
);
}
}
}

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>