Compare commits
350 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10677f3705 | ||
|
|
9d4d09810a | ||
|
|
28a1bd5219 | ||
|
|
1197c10592 | ||
|
|
3bb92d452b | ||
|
|
2bfabfd838 | ||
|
|
9876cd547f | ||
|
|
4a8d261a82 | ||
|
|
c4823f1c37 | ||
|
|
6e9238329c | ||
|
|
c86cb962b9 | ||
|
|
56935a7210 | ||
|
|
cdc08e7e8a | ||
|
|
ca7794e6f2 | ||
|
|
3b5cae01e0 | ||
|
|
edb8dc58f7 | ||
|
|
3fc69f16d5 | ||
|
|
bd3fdcab26 | ||
|
|
201191e96d | ||
|
|
f545eafa77 | ||
|
|
5583c59e96 | ||
|
|
fbcf9c900c | ||
|
|
0801dea6e6 | ||
|
|
217514af66 | ||
|
|
e0191c573d | ||
|
|
ef4b53b337 | ||
|
|
acf2e4360f | ||
|
|
3227daddaf | ||
|
|
6e40b7f25b | ||
|
|
0dd87bbf78 | ||
|
|
dcfdc7d0ea | ||
|
|
e79097603f | ||
|
|
ffd8f9951f | ||
|
|
e27370cf32 | ||
|
|
405c4d1706 | ||
|
|
f7e081ba5d | ||
|
|
c71deb5051 | ||
|
|
edab722a76 | ||
|
|
2d280bd995 | ||
|
|
27e3c6553e | ||
|
|
6258a9cff9 | ||
|
|
a72f497581 | ||
|
|
311d3dd635 | ||
|
|
e80b3e4542 | ||
|
|
82c96555dc | ||
|
|
75b6e69d34 | ||
|
|
532b5f7c33 | ||
|
|
a841419c30 | ||
|
|
730a56380a | ||
|
|
97aa974443 | ||
|
|
b2eee8bde7 | ||
|
|
3cbe932248 | ||
|
|
26d5504a2f | ||
|
|
b163a0fe77 | ||
|
|
523e713d7a | ||
|
|
c7cf634a94 | ||
|
|
c8c14396f1 | ||
|
|
e72ccaf440 | ||
|
|
0b7e07ebab | ||
|
|
37e19d9a60 | ||
|
|
8f533bc576 | ||
|
|
096a9561ed | ||
|
|
c6ac9376fc | ||
|
|
fd55cf6996 | ||
|
|
0359705361 | ||
|
|
e31a7e5236 | ||
|
|
bb477908ef | ||
|
|
26175fbe1b | ||
|
|
67bc59f6b6 | ||
|
|
7b358b1bbb | ||
|
|
0387d5bdd1 | ||
|
|
2ddf624f7d | ||
|
|
74b34661a5 | ||
|
|
dc9765ef58 | ||
|
|
a50e66faf4 | ||
|
|
0388738e02 | ||
|
|
d33e38012a | ||
|
|
785d0b21c6 | ||
|
|
db12cd92b7 | ||
|
|
52261f99d7 | ||
|
|
692dc154ef | ||
|
|
22101d8f4a | ||
|
|
f68db90b1f | ||
|
|
5e680531da | ||
|
|
93cd31018e | ||
|
|
277c570723 | ||
|
|
f1419a75f6 | ||
|
|
3af08a4727 | ||
|
|
a535cea85f | ||
|
|
29e443ed76 | ||
|
|
f95cddd05a | ||
|
|
ae28de4159 | ||
|
|
39de2c1d25 | ||
|
|
22570e08aa | ||
|
|
3b4ef4d238 | ||
|
|
c5a71c4304 | ||
|
|
4f37c2cb73 | ||
|
|
c1ec97055c | ||
|
|
086c71126f | ||
|
|
10a78c1c94 | ||
|
|
cf6021d898 | ||
|
|
ff322cd2dd | ||
|
|
1a96d3c38e | ||
|
|
cfe84963fa | ||
|
|
d908a599b1 | ||
|
|
9b3ddb8da3 | ||
|
|
56d994a69d | ||
|
|
67cd17c604 | ||
|
|
f4fb7eb8b7 | ||
|
|
b9021e4331 | ||
|
|
3583836d3e | ||
|
|
278815119f | ||
|
|
1bb678e455 | ||
|
|
b9e5fc604b | ||
|
|
b65e8c48ce | ||
|
|
e59bc1a08e | ||
|
|
ff994629de | ||
|
|
a458b9bc88 | ||
|
|
2834e25151 | ||
|
|
c2582fe055 | ||
|
|
0980219c8d | ||
|
|
c5f158f1cf | ||
|
|
62afc023c8 | ||
|
|
52ca84946b | ||
|
|
e64fb39c32 | ||
|
|
1066598150 | ||
|
|
caed8c2cf0 | ||
|
|
663be2402d | ||
|
|
6fd24c842f | ||
|
|
302631c4fa | ||
|
|
8a94623b2b | ||
|
|
016dfdb455 | ||
|
|
097415385e | ||
|
|
8e6c6e04a3 | ||
|
|
81a30e580e | ||
|
|
676efe7253 | ||
|
|
39e0e77824 | ||
|
|
363f5be8ff | ||
|
|
df15fa2f0e | ||
|
|
e8c1fbb86f | ||
|
|
df986b9ecf | ||
|
|
daabf4bab9 | ||
|
|
3095948024 | ||
|
|
fad289305f | ||
|
|
97d8f07e0d | ||
|
|
dc374c7ce9 | ||
|
|
1584475bc3 | ||
|
|
8f1db25c5c | ||
|
|
092b536009 | ||
|
|
f95bbaa0f7 | ||
|
|
bc1f6464d3 | ||
|
|
b828cd5975 | ||
|
|
766b4f7971 | ||
|
|
45e2ffd71e | ||
|
|
98757c3f11 | ||
|
|
9aed6d350b | ||
|
|
ca6ce6db32 | ||
|
|
1c9a6a02af | ||
|
|
ba9bafcb5f | ||
|
|
0628394122 | ||
|
|
99b2cd2ad0 | ||
|
|
707a6ecbaa | ||
|
|
aa2bc40f03 | ||
|
|
5e00e76c4b | ||
|
|
0dba992dd4 | ||
|
|
b6f61cac9b | ||
|
|
0e41945a8a | ||
|
|
a05e037308 | ||
|
|
f40576c39d | ||
|
|
9fc810182a | ||
|
|
dd7a52ba08 | ||
|
|
fa6d2a3080 | ||
|
|
36efc0c877 | ||
|
|
e9efcf1b92 | ||
|
|
f4ad1ec8e7 | ||
|
|
0027c21630 | ||
|
|
6173cab99f | ||
|
|
80c8097a71 | ||
|
|
ba3d577125 | ||
|
|
8ce4ebb16e | ||
|
|
4358ff2338 | ||
|
|
53f9eb083d | ||
|
|
0a3a982cb9 | ||
|
|
c30239b3a8 | ||
|
|
183834689d | ||
|
|
5da2f3279b | ||
|
|
ec7d87e757 | ||
|
|
4155f69e3c | ||
|
|
473e93ea16 | ||
|
|
fd1941cc3e | ||
|
|
dbb51b58db | ||
|
|
e7d00cfe54 | ||
|
|
63453fa962 | ||
|
|
689eb7f87b | ||
|
|
67a4646a50 | ||
|
|
a4e0535464 | ||
|
|
61495cd428 | ||
|
|
9e7b7415a5 | ||
|
|
06a462d48c | ||
|
|
d3e6e415b9 | ||
|
|
7e633d31a0 | ||
|
|
0fcf006484 | ||
|
|
48e5b1686f | ||
|
|
608d879c80 | ||
|
|
66055f1d7c | ||
|
|
1120bff34d | ||
|
|
24547e67bf | ||
|
|
f8c7285f56 | ||
|
|
82eb6a4568 | ||
|
|
a3f1f7c78d | ||
|
|
c377c4a52b | ||
|
|
e5a74cf43c | ||
|
|
ccf2bf84da | ||
|
|
fbf3d97d57 | ||
|
|
6da0f82ddd | ||
|
|
4c3df2e1e1 | ||
|
|
39e10ff01c | ||
|
|
0b29c6e5a4 | ||
|
|
cd3585be58 | ||
|
|
4e1f91f4d5 | ||
|
|
272c2e5303 | ||
|
|
2a82b09f7b | ||
|
|
fd26492577 | ||
|
|
528b90b694 | ||
|
|
9c7961ff6b | ||
|
|
954ed6457c | ||
|
|
5a8fc2dabc | ||
|
|
ce965ba5e1 | ||
|
|
4b9a036e5e | ||
|
|
4576f378cc | ||
|
|
4c65daa995 | ||
|
|
9159e14dd9 | ||
|
|
da661c229c | ||
|
|
973f09f98a | ||
|
|
fef370ad88 | ||
|
|
bc46b7172d | ||
|
|
71f546b467 | ||
|
|
a66f66c8ac | ||
|
|
c2c6ca22db | ||
|
|
b29440556a | ||
|
|
4104f6f772 | ||
|
|
01c56dabdf | ||
|
|
fafd8f8ee6 | ||
|
|
e2033eee23 | ||
|
|
780761664d | ||
|
|
0cfa737eff | ||
|
|
6463898c5d | ||
|
|
7d4fffa8b6 | ||
|
|
bcc415ccb3 | ||
|
|
827fead347 | ||
|
|
36cdc7dd1c | ||
|
|
99dceda8ac | ||
|
|
9d27f111bf | ||
|
|
69e0906491 | ||
|
|
1d48171fd5 | ||
|
|
cb0a3e3edf | ||
|
|
2b3915a91f | ||
|
|
9a403ba0ed | ||
|
|
0f35885d1c | ||
|
|
e3e07b6bfe | ||
|
|
84a6d1db71 | ||
|
|
8cee50299f | ||
|
|
5a78cbef02 | ||
|
|
ae66a781d1 | ||
|
|
5ae3b66e06 | ||
|
|
c3f1cee5d6 | ||
|
|
5552c42e37 | ||
|
|
6f146b888b | ||
|
|
56c09eae90 | ||
|
|
6883864e2d | ||
|
|
15bc395454 | ||
|
|
41997d5fe0 | ||
|
|
ed259cd130 | ||
|
|
1dc027cf49 | ||
|
|
b2abcda111 | ||
|
|
d66eaf8855 | ||
|
|
78cfd82fdd | ||
|
|
8ad44b405d | ||
|
|
4ce4288f68 | ||
|
|
d635555576 | ||
|
|
44999557c0 | ||
|
|
5d64bab719 | ||
|
|
4d3d8b643a | ||
|
|
915e8cf072 | ||
|
|
3c18fd7636 | ||
|
|
6c00ac43fc | ||
|
|
5d9a597d8d | ||
|
|
92930955c3 | ||
|
|
145482ea30 | ||
|
|
6fdb1e3356 | ||
|
|
55dff81b9f | ||
|
|
ed37972b99 | ||
|
|
c6b37307b0 | ||
|
|
5d719ba235 | ||
|
|
c19795cce0 | ||
|
|
df8f44d77d | ||
|
|
a4eff45534 | ||
|
|
b2b12be3b0 | ||
|
|
9c77c53366 | ||
|
|
1449f165dd | ||
|
|
94216cf745 | ||
|
|
120e179fb8 | ||
|
|
f10114ee17 | ||
|
|
8a059e0fbb | ||
|
|
6263788d6a | ||
|
|
6ffb3136d4 | ||
|
|
b65b01fe3d | ||
|
|
b9c134654f | ||
|
|
d1a1342587 | ||
|
|
1ec2ac472a | ||
|
|
9894322c17 | ||
|
|
7edbf4ffc8 | ||
|
|
2b1d186611 | ||
|
|
70c49922b0 | ||
|
|
30d6a4d9eb | ||
|
|
033b2b9ba0 | ||
|
|
25aec80e4c | ||
|
|
cf3d52772d | ||
|
|
f78f303a79 | ||
|
|
02cffa01e2 | ||
|
|
c2f2a5e52f | ||
|
|
d8e19415e3 | ||
|
|
387dc2f59c | ||
|
|
ec3660a86d | ||
|
|
36fb23d467 | ||
|
|
33df456cfd | ||
|
|
7a6fe5ed5f | ||
|
|
558b10499b | ||
|
|
1fb3698ba2 | ||
|
|
89f26bbc6b | ||
|
|
bbd8615cda | ||
|
|
93132f5d7b | ||
|
|
179514ddf1 | ||
|
|
4b9cff2271 | ||
|
|
c3649a9c80 | ||
|
|
9a66b9003f | ||
|
|
34e32403b0 | ||
|
|
c2e34a8b0e | ||
|
|
d0ba4b6702 | ||
|
|
eb16025800 | ||
|
|
9f06c9a051 | ||
|
|
641122b16f | ||
|
|
fbe8708378 | ||
|
|
21c7b486ff | ||
|
|
c33728d418 | ||
|
|
a9dacd561c | ||
|
|
051e15215d | ||
|
|
fee8f58c0a | ||
|
|
cc036cf3c5 | ||
|
|
4e51517ddb |
17
.github/resources/export-options-ad-hoc.plist
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>ad-hoc</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>com.8bit.bitwarden</key>
|
||||
<string>Ad hoc: Bitwarden 2020</string>
|
||||
<key>com.8bit.bitwarden.autofill</key>
|
||||
<string>Ad hoc: Autofill 2020</string>
|
||||
<key>com.8bit.bitwarden.find-login-action-extension</key>
|
||||
<string>Ad hoc: Extension 2020</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
17
.github/resources/export-options-app-store.plist
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>app-store</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>com.8bit.bitwarden</key>
|
||||
<string>Dist: Bitwarden 2020</string>
|
||||
<key>com.8bit.bitwarden.autofill</key>
|
||||
<string>Dist: Autofill 2020</string>
|
||||
<key>com.8bit.bitwarden.find-login-action-extension</key>
|
||||
<string>Dist: Extension 2020</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
13
.github/scripts/android/build.ps1
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $configuration
|
||||
)
|
||||
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
$androidPath = $($rootPath + "/src/Android/Android.csproj");
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Build $configuration Configuration"
|
||||
Write-Output "########################################"
|
||||
|
||||
msbuild "$($androidPath)" "/p:Configuration=$configuration"
|
||||
75
.github/scripts/android/clean-fdroid.ps1
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
|
||||
$androidPath = $($rootPath + "/src/Android/Android.csproj");
|
||||
$appPath = $($rootPath + "/src/App/App.csproj");
|
||||
|
||||
$androidManifest = $($rootPath + "/src/Android/Properties/AndroidManifest.xml");
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Clean Android and App"
|
||||
Write-Output "########################################"
|
||||
|
||||
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
|
||||
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Backup project files"
|
||||
Write-Output "########################################"
|
||||
|
||||
Copy-Item $androidManifest $($androidManifest + ".original");
|
||||
Copy-Item $androidPath $($androidPath + ".original");
|
||||
Copy-Item $appPath $($appPath + ".original");
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Cleanup Android Manifest"
|
||||
Write-Output "########################################"
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($androidManifest);
|
||||
|
||||
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
||||
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
|
||||
|
||||
$firebaseReceiver1=$xml.SelectSingleNode(`
|
||||
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdInternalReceiver']", `
|
||||
$nsAndroid);
|
||||
$firebaseReceiver1.ParentNode.RemoveChild($firebaseReceiver1);
|
||||
|
||||
$firebaseReceiver2=$xml.SelectSingleNode(`
|
||||
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdReceiver']", `
|
||||
$nsAndroid);
|
||||
$firebaseReceiver2.ParentNode.RemoveChild($firebaseReceiver2);
|
||||
|
||||
$xml.Save($androidManifest);
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Uninstall from Android.csproj"
|
||||
Write-Output "########################################"
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($androidPath);
|
||||
|
||||
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
||||
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
|
||||
|
||||
$firebaseNode=$xml.SelectSingleNode(`
|
||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
|
||||
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
|
||||
|
||||
$safetyNetNode=$xml.SelectSingleNode(`
|
||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
|
||||
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
|
||||
|
||||
$xml.Save($androidPath);
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Uninstall from App.csproj"
|
||||
Write-Output "########################################"
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($appPath);
|
||||
|
||||
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
||||
|
||||
$xml.Save($appPath);
|
||||
24
.github/scripts/android/compile-fdroid.sh
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd $GITHUB_WORKSPACE
|
||||
mkdir dist
|
||||
cp CNAME ./dist
|
||||
cd store
|
||||
chmod 600 fdroid/config.py fdroid/keystore.jks
|
||||
mkdir -p temp/fdroid
|
||||
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
|
||||
cd fdroid
|
||||
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
|
||||
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
|
||||
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
|
||||
mkdir -p repo
|
||||
curl -Lo repo/com.x8bit.bitwarden-fdroid.apk \
|
||||
https://github.com/bitwarden/mobile/releases/download/$RELEASE_TAG_NAME/com.x8bit.bitwarden-fdroid.apk
|
||||
fdroid update
|
||||
fdroid server update
|
||||
cd ..
|
||||
rm -rf temp/fdroid/archive
|
||||
mv -v temp/fdroid ../dist
|
||||
cd fdroid
|
||||
cp index.html btn.png qr.png ../../dist/fdroid
|
||||
cd $GITHUB_WORKSPACE
|
||||
22
.github/scripts/android/decrypt-secrets.ps1
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
|
||||
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
|
||||
|
||||
$appKeystorePlayFilename = "app_play-keystore.jks";
|
||||
$appKeystorePlayPath = $($rootPath + "/src/Android/$appKeystorePlayFilename");
|
||||
$appKeystoreUploadFilename = "app_upload-keystore.jks";
|
||||
$appKeystoreUploadPath = $($rootPath + "/src/Android/$appKeystoreUploadFilename");
|
||||
$appKeystoreFdroidFilename = "app_fdroid-keystore.jks";
|
||||
$appKeystoreFdroidPath = $($rootPath + "/src/Android/$appKeystoreFdroidFilename");
|
||||
$googleServicesFilename = "google-services.json";
|
||||
$googleServicesPath = $($rootPath + "/src/Android/$googleServicesFilename");
|
||||
|
||||
Invoke-Expression `
|
||||
"& `"$decryptSecretPath`" -filename $($appKeystorePlayFilename + ".gpg") -output $($appKeystorePlayPath)"
|
||||
Invoke-Expression `
|
||||
"& `"$decryptSecretPath`" -filename $($appKeystoreUploadFilename + ".gpg") -output $($appKeystoreUploadPath)"
|
||||
Invoke-Expression `
|
||||
"& `"$decryptSecretPath`" -filename $($appKeystoreFdroidFilename + ".gpg") -output $($appKeystoreFdroidPath)"
|
||||
Invoke-Expression `
|
||||
"& `"$decryptSecretPath`" -filename $($googleServicesFilename + ".gpg") -output $($googleServicesPath)"
|
||||
Invoke-Expression "& `"$decryptSecretPath`" -filename play_creds.json.gpg"
|
||||
9
.github/scripts/android/deploy-play.ps1
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
|
||||
|
||||
$publisherPath = $($rootPath + "/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll");
|
||||
$credsPath = $($homePath + "/secrets/play_creds.json");
|
||||
$aabPath = $($rootPath + "/com.x8bit.bitwarden.aab");
|
||||
$track = "internal";
|
||||
|
||||
dotnet $publisherPath $credsPath $aabPath $track
|
||||
16
.github/scripts/android/increment-version.ps1
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
$buildNumber = 3000 + [int]$env:GITHUB_RUN_NUMBER;
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Setting Version Code $buildNumber"
|
||||
Write-Output "########################################"
|
||||
|
||||
$androidManifest = $($rootPath + "/src/Android/Properties/AndroidManifest.xml");
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($androidManifest);
|
||||
|
||||
$node=$xml.SelectNodes("/manifest");
|
||||
$node.SetAttribute("android:versionCode", [string]$buildNumber);
|
||||
|
||||
$xml.Save($androidManifest);
|
||||
23
.github/scripts/android/sign-fdroid.ps1
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
|
||||
$androidPath = $($rootPath + "/src/Android/Android.csproj");
|
||||
|
||||
$appKeystoreFdroidFilename = "app_fdroid-keystore.jks";
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Sign FDroid Configuration"
|
||||
Write-Output "########################################"
|
||||
|
||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
|
||||
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:FDROID_KEYSTORE_PASSWORD)" `
|
||||
"/p:AndroidSigningKeyStore=$($appKeystoreFdroidFilename)" `
|
||||
"/p:AndroidSigningStorePass=$($env:FDROID_KEYSTORE_PASSWORD)" "/v:quiet"
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Copy FDroid apk to project root"
|
||||
Write-Output "########################################"
|
||||
|
||||
$signedApkPath = $($rootPath + "/src/Android/bin/FDroid/com.x8bit.bitwarden-Signed.apk");
|
||||
$signedApkDestPath = $($rootPath + "/com.x8bit.bitwarden-fdroid.apk");
|
||||
|
||||
Copy-Item $signedApkPath $signedApkDestPath
|
||||
42
.github/scripts/android/sign-play.ps1
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
|
||||
$androidPath = $($rootPath + "/src/Android/Android.csproj");
|
||||
|
||||
$appKeystorePlayFilename = "app_play-keystore.jks";
|
||||
$appKeystoreUploadFilename = "app_upload-keystore.jks";
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Sign Google Play Bundle Release Configuration"
|
||||
Write-Output "########################################"
|
||||
|
||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
|
||||
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:UPLOAD_KEYSTORE_PASSWORD)" `
|
||||
"/p:AndroidSigningKeyStore=$($appKeystoreUploadFilename)" `
|
||||
"/p:AndroidSigningStorePass=$($env:UPLOAD_KEYSTORE_PASSWORD)" "/p:AndroidPackageFormat=aab" "/v:quiet"
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Copy Google Play Bundle to project root"
|
||||
Write-Output "########################################"
|
||||
|
||||
$signedAabPath = $($rootPath + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
|
||||
$signedAabDestPath = $($rootPath + "/com.x8bit.bitwarden.aab");
|
||||
|
||||
Copy-Item $signedAabPath $signedAabDestPath
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Sign APK Release Configuration"
|
||||
Write-Output "########################################"
|
||||
|
||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
|
||||
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:PLAY_KEYSTORE_PASSWORD)" `
|
||||
"/p:AndroidSigningKeyStore=$($appKeystorePlayFilename)" `
|
||||
"/p:AndroidSigningStorePass=$($env:PLAY_KEYSTORE_PASSWORD)" "/v:quiet"
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Copy Release APK to project root"
|
||||
Write-Output "########################################"
|
||||
|
||||
$signedApkPath = $($rootPath + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
|
||||
$signedApkDestPath = $($rootPath + "/com.x8bit.bitwarden.apk");
|
||||
|
||||
Copy-Item $signedApkPath $signedApkDestPath
|
||||
29
.github/scripts/decrypt-secret.ps1
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $filename,
|
||||
[string] $output
|
||||
)
|
||||
|
||||
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
|
||||
$rootPath = $env:GITHUB_WORKSPACE
|
||||
|
||||
$secretInputPath = $rootPath + "/.github/secrets"
|
||||
$input = $secretInputPath + "/" + $filename
|
||||
|
||||
$passphrase = $env:DECRYPT_FILE_PASSWORD
|
||||
$secretOutputPath = $homePath + "/secrets"
|
||||
|
||||
if ([string]::IsNullOrEmpty($output)) {
|
||||
if ($filename.EndsWith(".gpg")) {
|
||||
$output = $secretOutputPath + "/" + $filename.TrimEnd(".gpg")
|
||||
} else {
|
||||
$output = $secretOutputPath + "/" + $filename + ".plaintext"
|
||||
}
|
||||
}
|
||||
|
||||
if (!(Test-Path -Path $secretOutputPath))
|
||||
{
|
||||
New-Item -ItemType Directory -Path $secretOutputPath
|
||||
}
|
||||
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$passphrase" --output $output $input
|
||||
29
.github/scripts/ios/build.ps1
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $configuration,
|
||||
[string] $platform = "iPhone",
|
||||
[switch] $archive
|
||||
)
|
||||
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
$iosPath = $($rootPath + "/src/iOS/iOS.csproj");
|
||||
|
||||
if ($archive)
|
||||
{
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Archive $configuration Configuration for $platform Platform"
|
||||
Write-Output "########################################"
|
||||
msbuild "$($iosPath)" "/p:Platform=$platform" "/p:Configuration=$configuration" `
|
||||
"/p:ArchiveOnBuild=true" "/t:`"Build`""
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Done"
|
||||
Write-Output "########################################"
|
||||
ls ~/Library/Developer/Xcode/Archives
|
||||
} else
|
||||
{
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Build $configuration Configuration for $platform Platform"
|
||||
Write-Output "########################################"
|
||||
msbuild "$($iosPath)" "/p:Platform=$platform" "/p:Configuration=$configuration" "/t:`"Build`""
|
||||
}
|
||||
9
.github/scripts/ios/decrypt-secrets.ps1
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
|
||||
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
|
||||
|
||||
Invoke-Expression "& `"$decryptSecretPath`" -filename bitwarden-mobile-key.p12.gpg"
|
||||
Invoke-Expression "& `"$decryptSecretPath`" -filename iphone-distribution-cert.p12.gpg"
|
||||
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_autofill.mobileprovision.gpg"
|
||||
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_bitwarden.mobileprovision.gpg"
|
||||
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_extension.mobileprovision.gpg"
|
||||
5
.github/scripts/ios/deploy-app-store.ps1
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
$ipaPath = "$rootPath/bitwarden-export/Bitwarden.ipa"
|
||||
|
||||
xcrun altool --upload-app --type ios --file "$ipaPath" `
|
||||
--username "$env:APPLE_ID_USERNAME" --password "$env:APPLE_ID_PASSWORD"
|
||||
13
.github/scripts/ios/export-ipa.ps1
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $method
|
||||
)
|
||||
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
|
||||
|
||||
$exportOptionsPath = "$rootPath/.github/resources/export-options-$method.plist";
|
||||
$archivePath = "$homePath/Library/Developer/Xcode/Archives/*/*.xcarchive";
|
||||
$exportPath = "$rootPath/bitwarden-export";
|
||||
|
||||
xcodebuild -exportArchive -archivePath $archivePath -exportPath $exportPath -exportOptionsPlist $exportOptionsPath
|
||||
26
.github/scripts/ios/increment-version.ps1
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
$rootPath = $env:GITHUB_WORKSPACE;
|
||||
$buildNumber = 100 + [int]$env:GITHUB_RUN_NUMBER;
|
||||
|
||||
$bitwardenInfo = $($rootPath + "/src/iOS/Info.plist");
|
||||
$extensionInfo = $($rootPath + "/src/iOS.Extension/Info.plist");
|
||||
$autofillInfo = $($rootPath + "/src/iOS.Autofill/Info.plist");
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Setting CFBundleVersion $buildNumber"
|
||||
Write-Output "########################################"
|
||||
|
||||
function Update-Version($file) {
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($file);
|
||||
|
||||
Select-Xml -xml $xml -XPath "//dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" |
|
||||
%{
|
||||
$_.Node.InnerXml = $buildNumber
|
||||
}
|
||||
|
||||
$xml.Save($file);
|
||||
}
|
||||
|
||||
Update-Version $bitwardenInfo
|
||||
Update-Version $extensionInfo
|
||||
Update-Version $autofillInfo
|
||||
13
.github/scripts/ios/setup-keychain.ps1
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
|
||||
$secretsPath = $homePath + "/secrets"
|
||||
|
||||
$mobileKeyPath = $($secretsPath + "/bitwarden-mobile-key.p12");
|
||||
$distCertPath = $($secretsPath + "/iphone-distribution-cert.p12");
|
||||
|
||||
security create-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
|
||||
security set-keychain-settings -lut 1200 build.keychain
|
||||
security import $mobileKeyPath -k build.keychain -P $env:MOBILE_KEY_PASSWORD -T /usr/bin/codesign -T /usr/bin/security
|
||||
security import $distCertPath -k build.keychain -P $env:DIST_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $env:KEYCHAIN_PASSWORD build.keychain
|
||||
21
.github/scripts/ios/setup-profiles.ps1
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
|
||||
$secretsPath = $homePath + "/secrets"
|
||||
|
||||
$autofillProfilePath = $($secretsPath + "/dist_autofill.mobileprovision");
|
||||
$bitwardenProfilePath = $($secretsPath + "/dist_bitwarden.mobileprovision");
|
||||
$extensionProfilePath = $($secretsPath + "/dist_extension.mobileprovision");
|
||||
$profilesDirPath = "~/Library/MobileDevice/Provisioning Profiles"
|
||||
|
||||
if (!(Test-Path -Path $profilesDirPath))
|
||||
{
|
||||
New-Item -ItemType Directory -Path $profilesDirPath
|
||||
}
|
||||
|
||||
$autofill_uuid = grep UUID -A1 -a $autofillProfilePath | grep -io "[-A-F0-9]\{36\}"
|
||||
Copy-Item $autofillProfilePath -destination "$profilesDirPath/$autofill_uuid.mobileprovision"
|
||||
|
||||
$bitwarden_uuid = grep UUID -A1 -a $bitwardenProfilePath | grep -io "[-A-F0-9]\{36\}"
|
||||
Copy-Item $bitwardenProfilePath -destination "$profilesDirPath/$bitwarden_uuid.mobileprovision"
|
||||
|
||||
$extension_uuid = grep UUID -A1 -a $extensionProfilePath | grep -io "[-A-F0-9]\{36\}"
|
||||
Copy-Item $extensionProfilePath -destination "$profilesDirPath/$extension_uuid.mobileprovision"
|
||||
BIN
.github/secrets/app_fdroid-keystore.jks.gpg
vendored
Normal file
BIN
.github/secrets/app_play-keystore.jks.gpg
vendored
Normal file
BIN
.github/secrets/app_upload-keystore.jks.gpg
vendored
Normal file
BIN
.github/secrets/bitwarden-mobile-key.p12.gpg
vendored
Normal file
BIN
.github/secrets/dist_autofill.mobileprovision.gpg
vendored
Normal file
BIN
.github/secrets/dist_bitwarden.mobileprovision.gpg
vendored
Normal file
BIN
.github/secrets/dist_extension.mobileprovision.gpg
vendored
Normal file
3
.github/secrets/google-services.json.gpg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<EFBFBD>
|
||||
K<>Y#<23>(<28><><EFBFBD><EFBFBD>EI߄T?)l<><6C><EFBFBD><18><><10>"=<3D>|<7C>'e<><0E>m<EFBFBD>/~<7E><>'F<><46>><3E><><EFBFBD><EFBFBD>l<EFBFBD>b<EFBFBD>[<5B>+R<><52>iL<69><4C>"<22><><EFBFBD>~V:<3A><>p<EFBFBD>a<17>ڵel%8t<38><74>튖<EFBFBD>y<<3C>n<EFBFBD><6E><EFBFBD>aU<61>w<16>JD<4A><44><1F><>We<57>9<EFBFBD><39><EFBFBD><EFBFBD><x8d<38>O<EFBFBD>j\<14>ד<EFBFBD><D793><EFBFBD>Vq<56><71>
|
||||
Ǻ<EFBFBD>-<2D>#<23><><11><>]$<24>(<28>l,<2C>Br<42><02><>d<><64><EFBFBD>a-<2D><><EFBFBD>:<3A><>:<3A><04>9b,!Em<02><19><>Qf<>D<EFBFBD>g<EFBFBD><06><0E>x(P<>ȡ~<7E><EFBFBD><CDB9> <09><>[<06><>!:<3A>;f<><66>
|
||||
BIN
.github/secrets/iphone-distribution-cert.p12.gpg
vendored
Normal file
BIN
.github/secrets/play_creds.json.gpg
vendored
Normal file
BIN
.github/secrets/store_fdroid-keystore.jks.gpg
vendored
Normal file
294
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
- 'gh-pages'
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
|
||||
cloc:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up cloc
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install cloc
|
||||
|
||||
- name: Print lines of code
|
||||
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
|
||||
|
||||
android:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Set up MSBuild
|
||||
uses: microsoft/setup-msbuild@v1
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
nuget help
|
||||
msbuild -version
|
||||
dotnet --info
|
||||
Write-Output "GitHub ref: $env:GITHUB_REF"
|
||||
Write-Output "GitHub event: $env:GITHUB_EVENT"
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT: ${{ github.event_name }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Decrypt secrets
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/android/decrypt-secrets.ps1
|
||||
shell: pwsh
|
||||
env:
|
||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||
|
||||
- name: Increment version
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/android/increment-version.ps1
|
||||
shell: pwsh
|
||||
|
||||
- name: Restore packages
|
||||
run: nuget restore
|
||||
|
||||
- name: Build Play Store publisher
|
||||
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
|
||||
|
||||
- name: Build for Play Store
|
||||
run: ./.github/scripts/android/build.ps1 -configuration Release
|
||||
shell: pwsh
|
||||
|
||||
- name: Sign for Play Store
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/android/sign-play.ps1
|
||||
shell: pwsh
|
||||
env:
|
||||
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
|
||||
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
|
||||
|
||||
- name: Upload Play Store .aab artifact
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.aab
|
||||
path: ./com.x8bit.bitwarden.aab
|
||||
|
||||
- name: Upload Play Store .apk artifact
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.apk
|
||||
path: ./com.x8bit.bitwarden.apk
|
||||
|
||||
- name: Clean for F-Droid
|
||||
run: ./.github/scripts/android/clean-fdroid.ps1
|
||||
shell: pwsh
|
||||
|
||||
- name: Restore packages
|
||||
run: nuget restore
|
||||
|
||||
- name: Build for F-Droid
|
||||
run: ./.github/scripts/android/build.ps1 -configuration FDroid
|
||||
shell: pwsh
|
||||
|
||||
- name: Sign for F-Droid
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/android/sign-fdroid.ps1
|
||||
shell: pwsh
|
||||
env:
|
||||
FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
|
||||
|
||||
- name: Upload F-Droid .apk artifact
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: com.x8bit.bitwarden-fdroid.apk
|
||||
path: ./com.x8bit.bitwarden-fdroid.apk
|
||||
|
||||
- name: Deploy to Play Store
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/android/deploy-play.ps1
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload release assets
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
hub release edit `
|
||||
-a ./com.x8bit.bitwarden.aab `
|
||||
-a ./com.x8bit.bitwarden.apk `
|
||||
-a ./com.x8bit.bitwarden-fdroid.apk `
|
||||
-m "Version $($env:RELEASE_TAG_NAME.TrimStart('v'))" `
|
||||
$env:RELEASE_TAG_NAME
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
|
||||
android-ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
needs: android
|
||||
|
||||
steps:
|
||||
- name: Set up Node
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '10.x'
|
||||
|
||||
- name: Set up F-Droid server
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
|
||||
|
||||
- name: Set up git credentials
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
run: |
|
||||
git config --global credential.helper store
|
||||
echo "https://${ACCESS_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
|
||||
git config --global user.email "ci@bitwarden.com"
|
||||
git config --global user.name "Bitwarden CI"
|
||||
|
||||
- name: Print environment
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
git --version
|
||||
Write-Output "GitHub ref: $env:GITHUB_REF"
|
||||
Write-Output "GitHub event: $env:GITHUB_EVENT"
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT: ${{ github.event_name }}
|
||||
|
||||
- name: Checkout repo
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node dependencies
|
||||
if: github.event_name == 'release'
|
||||
run: npm install
|
||||
|
||||
- name: Decrypt secrets
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
./.github/scripts/decrypt-secret.ps1 -filename store_fdroid-keystore.jks.gpg `
|
||||
-output ./store/fdroid/keystore.jks
|
||||
shell: pwsh
|
||||
env:
|
||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||
|
||||
- name: Compile for F-Droid Store
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
sudo chmod +x ./.github/scripts/android/compile-fdroid.sh
|
||||
./.github/scripts/android/compile-fdroid.sh
|
||||
env:
|
||||
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
|
||||
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
|
||||
- name: Deploy to gh-pages
|
||||
if: github.event_name == 'release'
|
||||
run: npm run deploy
|
||||
|
||||
ios:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Print environment
|
||||
run: |
|
||||
nuget help
|
||||
msbuild -version
|
||||
dotnet --info
|
||||
Write-Output "GitHub ref: $env:GITHUB_REF"
|
||||
Write-Output "GitHub event: $env:GITHUB_EVENT"
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT: ${{ github.event_name }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Decrypt secrets
|
||||
run: ./.github/scripts/ios/decrypt-secrets.ps1
|
||||
shell: pwsh
|
||||
env:
|
||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||
|
||||
- name: Increment version
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/ios/increment-version.ps1
|
||||
shell: pwsh
|
||||
|
||||
- name: Set up keychain
|
||||
run: ./.github/scripts/ios/setup-keychain.ps1
|
||||
shell: pwsh
|
||||
env:
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
|
||||
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
|
||||
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
|
||||
|
||||
- name: Set up provisioning profiles
|
||||
run: ./.github/scripts/ios/setup-profiles.ps1
|
||||
shell: pwsh
|
||||
|
||||
- name: Restore packages
|
||||
run: nuget restore
|
||||
|
||||
- name: Archive Build for App Store
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/ios/build.ps1 -configuration AppStore -platform iPhone -archive
|
||||
shell: pwsh
|
||||
|
||||
- name: Build for App Store
|
||||
if: github.ref != 'refs/heads/master'
|
||||
run: ./.github/scripts/ios/build.ps1 -configuration AppStore -platform iPhone
|
||||
shell: pwsh
|
||||
|
||||
- name: Export .ipa for App Store
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/ios/export-ipa.ps1 -method app-store
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload App Store .ipa artifact
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Bitwarden.ipa
|
||||
path: ./bitwarden-export/Bitwarden.ipa
|
||||
|
||||
- name: Deploy to App Store
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./.github/scripts/ios/deploy-app-store.ps1
|
||||
shell: pwsh
|
||||
env:
|
||||
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
|
||||
- name: Upload release assets
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
hub release edit `
|
||||
-a ./bitwarden-export/Bitwarden.ipa `
|
||||
-m "Version $($env:RELEASE_TAG_NAME.TrimStart('v'))" `
|
||||
$env:RELEASE_TAG_NAME
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
20
.gitignore
vendored
@@ -1,15 +1,28 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# Visual Studio (>=2015) project-specific, machine local files
|
||||
.vs/
|
||||
|
||||
# JetBrains Rider project-specific, machine local files
|
||||
.idea
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# ignore Xamarin.Android Resource.Designer.cs files
|
||||
**/*.Droid/**/[Rr]esource.[Dd]esigner.cs
|
||||
**/*.Android/**/[Rr]esource.[Dd]esigner.cs
|
||||
**/Android/**/[Rr]esource.[Dd]esigner.cs
|
||||
**/Droid/**/[Rr]esource.[Dd]esigner.cs
|
||||
|
||||
# Xamarin Components
|
||||
Components/
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
@@ -22,9 +35,6 @@ bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# Visual Studo 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
# How to Contribute
|
||||
|
||||
Contributions of all kinds are welcome!
|
||||
|
||||
Please visit our [Community Forums](https://community.bitwarden.com/) for general community discussion and the development roadmap.
|
||||
|
||||
Here is how you can get involved:
|
||||
|
||||
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
|
||||
|
||||
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
|
||||
|
||||
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
|
||||
|
||||
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
|
||||
|
||||
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
|
||||
|
||||
* **Translate:** See the localization (i10n) section below
|
||||
|
||||
## Contributor Agreement
|
||||
|
||||
Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/mobile) if you intend on contributing to any Github repository. Pull requests cannot be accepted and merged unless the author has signed the Contributor Agreement.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
* commit any pull requests against the `master` branch
|
||||
* include a link to your Community Forums post
|
||||
|
||||
# Localization (l10n)
|
||||
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
<!--
|
||||
<!-- Comment:
|
||||
Please do not submit feature requests. The [Community Forums][1] has a
|
||||
section for submitting, voting for, and discussing product feature requests.
|
||||
[1]: https://community.bitwarden.com
|
||||
-->
|
||||
|
||||
## Describe the Bug
|
||||
|
||||
<!-- Comment:
|
||||
A clear and concise description of what the bug is.
|
||||
-->
|
||||
|
||||
## Steps To Reproduce
|
||||
|
||||
<!-- Comment:
|
||||
How can we reproduce the behavior:
|
||||
-->
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. Click on '...'
|
||||
|
||||
## Expected Result
|
||||
|
||||
<!-- Comment:
|
||||
A clear and concise description of what you expected to happen.
|
||||
-->
|
||||
|
||||
## Actual Result
|
||||
|
||||
<!-- Comment:
|
||||
A clear and concise description of what is happening.
|
||||
-->
|
||||
|
||||
## Screenshots or Videos
|
||||
|
||||
<!-- Comment:
|
||||
If applicable, add screenshots and/or a short video to help explain your problem.
|
||||
-->
|
||||
|
||||
## Environment
|
||||
|
||||
- Device: [e.g. iPhone6]
|
||||
- Operating system: [e.g. iOS 8.1]
|
||||
- Build Version (go to "Settings" → "About" in the app): [e.g. 2.3.0 (2221)]
|
||||
- Is this a Beta release? [Y/N]
|
||||
|
||||
## Additional Context
|
||||
|
||||
<!-- Comment:
|
||||
Add any other context about the problem here.
|
||||
-->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
image:
|
||||
- Visual Studio 2019 Preview
|
||||
- Visual Studio 2019
|
||||
- Ubuntu1804
|
||||
|
||||
branches:
|
||||
@@ -55,7 +55,7 @@ before_build:
|
||||
- ps: |
|
||||
if($isWindows) {
|
||||
nuget restore
|
||||
if($env:KEYSTORE_DEC_SECRET -or $env:GOOGLE_SERVICES_DEC_SECRET -or $env:PLAY_DEC_SECRET) {
|
||||
if($env:UPLOAD_KEYSTORE_DEC_SECRET -or$env:KEYSTORE_DEC_SECRET -or $env:GOOGLE_SERVICES_DEC_SECRET -or $env:PLAY_DEC_SECRET) {
|
||||
nuget install secure-file -ExcludeVersion
|
||||
}
|
||||
if($env:GOOGLE_SERVICES_DEC_SECRET) {
|
||||
@@ -98,6 +98,7 @@ build_script:
|
||||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
|
||||
.\src\Android\ci-build-apks.ps1
|
||||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
|
||||
Push-AppveyorArtifact .\com.x8bit.bitwarden.aab
|
||||
Push-AppveyorArtifact .\com.x8bit.bitwarden.apk
|
||||
Push-AppveyorArtifact .\com.x8bit.bitwarden-fdroid.apk
|
||||
}
|
||||
@@ -109,12 +110,12 @@ on_success:
|
||||
npm run deploy
|
||||
fi
|
||||
- ps: |
|
||||
if($isWindows -and $env:PLAY_DEC_SECRET) {
|
||||
if($isWindows -and $env:PLAY_DEC_SECRET -and $env:APPVEYOR_REPO_BRANCH -eq 'master') {
|
||||
secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret $env:PLAY_DEC_SECRET
|
||||
cd store\google\Publisher\bin\Release\netcoreapp2.0
|
||||
dotnet Publisher.dll `
|
||||
$env:APPVEYOR_BUILD_FOLDER\store\google\Publisher\play_creds.json `
|
||||
$env:APPVEYOR_BUILD_FOLDER\com.x8bit.bitwarden.apk `
|
||||
$env:APPVEYOR_BUILD_FOLDER\com.x8bit.bitwarden.aab `
|
||||
alpha
|
||||
cd $env:APPVEYOR_BUILD_FOLDER
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
.editorconfig = .editorconfig
|
||||
.gitignore = .gitignore
|
||||
appveyor.yml = appveyor.yml
|
||||
.github\workflows\build.yml = .github\workflows\build.yml
|
||||
CONTRIBUTING.md = CONTRIBUTING.md
|
||||
crowdin.yml = crowdin.yml
|
||||
README.md = README.md
|
||||
@@ -238,22 +239,22 @@ Global
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.Build.0 = AppStore|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = AppStore|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = AppStore|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = AppStore|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
@@ -279,6 +280,7 @@ Global
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Deploy.0 = Debug|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|Any CPU.ActiveCfg = FDroid|iPhone
|
||||
@@ -306,6 +308,7 @@ Global
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.Deploy.0 = Debug|iPhone
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.ActiveCfg = Release|iPhone
|
||||
@@ -334,6 +337,7 @@ Global
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.Deploy.0 = Debug|iPhone
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|Any CPU.ActiveCfg = Release|iPhone
|
||||
|
||||
@@ -9,6 +9,7 @@ files:
|
||||
pt-PT: pt-PT
|
||||
pt-BR: pt-BR
|
||||
en-GB: en-GB
|
||||
en-IN: en-IN
|
||||
- source: /store/apple/en/copy.resx
|
||||
translation: /store/apple/%two_letters_code%/copy.resx
|
||||
update_option: update_as_unapproved
|
||||
@@ -19,6 +20,7 @@ files:
|
||||
pt-PT: pt-PT
|
||||
pt-BR: pt-BR
|
||||
en-GB: en-GB
|
||||
en-IN: en-IN
|
||||
- source: /store/google/en/copy.resx
|
||||
translation: /store/google/%two_letters_code%/copy.resx
|
||||
update_option: update_as_unapproved
|
||||
@@ -29,3 +31,4 @@ files:
|
||||
pt-BR: pt-BR
|
||||
pt-PT: pt-PT
|
||||
en-GB: en-GB
|
||||
en-IN: en-IN
|
||||
|
||||
@@ -4,6 +4,8 @@ using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using System;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
@@ -16,13 +18,13 @@ namespace Bit.Droid.Accessibility
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
base.OnCreate(bundle);
|
||||
LaunchMainActivity(Intent, 932473);
|
||||
HandleIntent(Intent, 932473);
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
{
|
||||
base.OnNewIntent(intent);
|
||||
LaunchMainActivity(intent, 489729);
|
||||
HandleIntent(intent, 489729);
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
@@ -33,7 +35,7 @@ namespace Bit.Droid.Accessibility
|
||||
protected override void OnResume()
|
||||
{
|
||||
base.OnResume();
|
||||
if(!Intent.HasExtra("uri"))
|
||||
if (!Intent.HasExtra("uri"))
|
||||
{
|
||||
Finish();
|
||||
return;
|
||||
@@ -44,7 +46,7 @@ namespace Bit.Droid.Accessibility
|
||||
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
|
||||
{
|
||||
base.OnActivityResult(requestCode, resultCode, data);
|
||||
if(data == null)
|
||||
if (data == null)
|
||||
{
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
}
|
||||
@@ -52,7 +54,7 @@ namespace Bit.Droid.Accessibility
|
||||
{
|
||||
try
|
||||
{
|
||||
if(data.GetStringExtra("canceled") != null)
|
||||
if (data.GetStringExtra("canceled") != null)
|
||||
{
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
}
|
||||
@@ -78,23 +80,38 @@ namespace Bit.Droid.Accessibility
|
||||
Finish();
|
||||
}
|
||||
|
||||
private void HandleIntent(Intent callingIntent, int requestCode)
|
||||
{
|
||||
if (callingIntent?.GetBooleanExtra("autofillTileClicked", false) ?? false)
|
||||
{
|
||||
Intent.RemoveExtra("autofillTileClicked");
|
||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
messagingService.Send("OnAutofillTileClick");
|
||||
Finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
LaunchMainActivity(callingIntent, requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void LaunchMainActivity(Intent callingIntent, int requestCode)
|
||||
{
|
||||
_lastQueriedUri = callingIntent?.GetStringExtra("uri");
|
||||
if(_lastQueriedUri == null)
|
||||
if (_lastQueriedUri == null)
|
||||
{
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
var now = DateTime.UtcNow;
|
||||
if(_lastLaunch.HasValue && (now - _lastLaunch.Value) <= TimeSpan.FromSeconds(2))
|
||||
if (_lastLaunch.HasValue && (now - _lastLaunch.Value) <= TimeSpan.FromSeconds(2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastLaunch = now;
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
if(!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
|
||||
if (!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
|
||||
{
|
||||
intent.PutExtra("uri", _lastQueriedUri);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Views.Accessibility;
|
||||
using Android.Widget;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
|
||||
namespace Bit.Droid.Accessibility
|
||||
@@ -12,62 +20,103 @@ namespace Bit.Droid.Accessibility
|
||||
public static Credentials LastCredentials = null;
|
||||
public static string SystemUiPackage = "com.android.systemui";
|
||||
public static string BitwardenTag = "bw_access";
|
||||
public static bool IsAutofillTileAdded = false;
|
||||
public static bool IsAccessibilityBroadcastReady = false;
|
||||
|
||||
// Be sure to keep these two sections sorted alphabetically
|
||||
public static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
||||
{
|
||||
new Browser("com.android.chrome", "url_bar"),
|
||||
new Browser("com.chrome.beta", "url_bar"),
|
||||
new Browser("org.chromium.chrome", "url_bar"),
|
||||
// [Section A] Entries also present in the list of Autofill Framework
|
||||
//
|
||||
// So keep them in sync with:
|
||||
// - AutofillHelpers.{TrustedBrowsers,CompatBrowsers}
|
||||
// - Resources/xml/autofillservice.xml
|
||||
new Browser("com.amazon.cloud9", "url"),
|
||||
new Browser("com.android.browser", "url"),
|
||||
new Browser("com.android.chrome", "url_bar"),
|
||||
// Rem. for "com.android.htmlviewer": doesn't have a URL bar, therefore not present here.
|
||||
new Browser("com.avast.android.secure.browser", "editor"),
|
||||
new Browser("com.avg.android.secure.browser", "editor"),
|
||||
new Browser("com.brave.browser", "url_bar"),
|
||||
new Browser("com.brave.browser_beta", "url_bar"),
|
||||
new Browser("com.brave.browser_default", "url_bar"),
|
||||
new Browser("com.brave.browser_dev", "url_bar"),
|
||||
new Browser("com.brave.browser_nightly", "url_bar"),
|
||||
new Browser("com.chrome.beta", "url_bar"),
|
||||
new Browser("com.chrome.canary", "url_bar"),
|
||||
new Browser("com.chrome.dev", "url_bar"),
|
||||
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
|
||||
new Browser("com.ecosia.android", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||
new Browser("com.kiwibrowser.browser", "url_bar"),
|
||||
new Browser("com.microsoft.emmx", "url_bar"),
|
||||
new Browser("com.mmbox.browser", "search_box"),
|
||||
new Browser("com.mmbox.xbrowser", "search_box"),
|
||||
new Browser("com.naver.whale", "url_bar"),
|
||||
new Browser("com.opera.browser", "url_field"),
|
||||
new Browser("com.opera.browser.beta", "url_field"),
|
||||
new Browser("com.opera.mini.native", "url_field"),
|
||||
new Browser("com.opera.mini.native.beta", "url_field"),
|
||||
new Browser("com.opera.touch", "addressbarEdit"),
|
||||
new Browser("com.chrome.dev", "url_bar"),
|
||||
new Browser("com.chrome.canary", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
||||
new Browser("org.iron.srware", "url_bar"),
|
||||
new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
|
||||
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
|
||||
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
|
||||
new Browser("com.yandex.browser", "bro_omnibar_address_title_text",
|
||||
new Browser("com.stoutner.privacybrowser.free", "url_edittext"),
|
||||
new Browser("com.stoutner.privacybrowser.standard", "url_edittext"),
|
||||
new Browser("com.vivaldi.browser", "url_bar"),
|
||||
new Browser("com.vivaldi.browser.snapshot", "url_bar"),
|
||||
new Browser("com.vivaldi.browser.sopranos", "url_bar"),
|
||||
new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title",
|
||||
(s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
|
||||
new Browser("org.mozilla.firefox", "url_bar_title"),
|
||||
new Browser("org.mozilla.firefox_beta", "url_bar_title"),
|
||||
new Browser("org.mozilla.fennec_aurora", "url_bar_title"),
|
||||
new Browser("org.mozilla.fennec_fdroid", "url_bar_title"),
|
||||
new Browser("com.z28j.feel", "g2"),
|
||||
new Browser("idm.internet.download.manager", "search"),
|
||||
new Browser("idm.internet.download.manager.adm.lite", "search"),
|
||||
new Browser("idm.internet.download.manager.plus", "search"),
|
||||
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("mark.via", "o"),
|
||||
new Browser("mark.via.gp", "o"),
|
||||
new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
||||
new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
||||
new Browser("org.bromite.bromite", "url_bar"),
|
||||
new Browser("org.bromite.chromium", "url_bar"),
|
||||
new Browser("org.chromium.chrome", "url_bar"),
|
||||
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
||||
new Browser("org.gnu.icecat", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
||||
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"), // [DEPRECATED ENTRY]
|
||||
new Browser("org.mozilla.fennec_aurora", "mozac_browser_toolbar_url_view,url_bar_title"), // [DEPRECATED ENTRY]
|
||||
new Browser("org.mozilla.fennec_fdroid", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
||||
new Browser("org.mozilla.firefox", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
||||
new Browser("org.mozilla.firefox_beta", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
||||
new Browser("org.mozilla.focus", "display_url"),
|
||||
new Browser("org.mozilla.klar", "display_url"),
|
||||
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("org.mozilla.rocket", "display_url"),
|
||||
new Browser("org.torproject.torbrowser", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0.3)
|
||||
new Browser("org.torproject.torbrowser_alpha", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0a8)
|
||||
new Browser("org.ungoogled.chromium.extensions.stable", "url_bar"),
|
||||
new Browser("org.ungoogled.chromium.stable", "url_bar"),
|
||||
|
||||
// [Section B] Entries only present here
|
||||
//
|
||||
// FIXME: Test the compatibility of these with Autofill Framework
|
||||
new Browser("acr.browser.barebones", "search"),
|
||||
new Browser("acr.browser.lightning", "search"),
|
||||
new Browser("com.feedback.browser.wjbrowser", "addressbar_url"),
|
||||
new Browser("com.ghostery.android.ghostery", "search_field"),
|
||||
new Browser("org.adblockplus.browser", "url_bar_title"),
|
||||
new Browser("com.htc.sense.browser", "title"),
|
||||
new Browser("com.amazon.cloud9", "url"),
|
||||
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
||||
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
||||
new Browser("com.jerky.browser2", "enterUrl"),
|
||||
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
|
||||
new Browser("com.linkbubble.playstore", "url_text"),
|
||||
new Browser("com.mx.browser", "address_editor_with_progress"),
|
||||
new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
|
||||
new Browser("com.linkbubble.playstore", "url_text"),
|
||||
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
|
||||
new Browser("acr.browser.lightning", "search"),
|
||||
new Browser("acr.browser.barebones", "search"),
|
||||
new Browser("com.microsoft.emmx", "url_bar"),
|
||||
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
|
||||
new Browser("mark.via.gp", "aw"),
|
||||
new Browser("org.bromite.bromite", "url_bar"),
|
||||
new Browser("com.kiwibrowser.browser", "url_bar"),
|
||||
new Browser("com.ecosia.android", "url_bar"),
|
||||
new Browser("com.qwant.liberty", "url_bar_title"),
|
||||
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
||||
new Browser("jp.co.fenrir.android.sleipnir", "url_text"),
|
||||
new Browser("jp.co.fenrir.android.sleipnir_black", "url_text"),
|
||||
new Browser("jp.co.fenrir.android.sleipnir_test", "url_text"),
|
||||
new Browser("com.vivaldi.browser", "url_bar"),
|
||||
new Browser("com.feedback.browser.wjbrowser", "addressbar_url"),
|
||||
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
||||
new Browser("org.iron.srware", "url_bar"),
|
||||
}.ToDictionary(n => n.PackageName);
|
||||
|
||||
// Known packages to skip
|
||||
@@ -91,11 +140,203 @@ namespace Bit.Droid.Accessibility
|
||||
"com.treydev.pns"
|
||||
};
|
||||
|
||||
// Be sure to keep these sections sorted alphabetically
|
||||
public static Dictionary<string, KnownUsernameField> KnownUsernameFields => new List<KnownUsernameField>
|
||||
{
|
||||
/**************************************************************************************
|
||||
* SECTION A ——— World-renowned web sites/applications
|
||||
*************************************************************************************/
|
||||
|
||||
// REM.: For this type of web sites/applications, the Top 100 (SimilarWeb, 2019)
|
||||
// and the Top 50 (Alexa Internet, 2020) are covered. National variants
|
||||
// have been added when available. Mobile and desktop versions supported.
|
||||
//
|
||||
// A few other popular web sites/applications have also been added.
|
||||
//
|
||||
// Could not be added, however:
|
||||
// web sites/applications that don't use an "id" attribute for their login field.
|
||||
|
||||
// NOTE: The case of OAuth compatible web sites/applications that also provide
|
||||
// a "user ID only" login page in this situation
|
||||
// was taken into account in the tests as well.
|
||||
|
||||
/*
|
||||
* A
|
||||
*/
|
||||
|
||||
// Amazon ——— ap_email_login = mobile / ap_email = desktop (amazon.co.jp currently uses ap_email in both cases, as of July 2020).
|
||||
new KnownUsernameField("amazon.ae", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.ca", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.cn", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.co.jp", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.co.uk", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.com", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.com.au", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.com.br", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.com.mx", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.com.tr", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.de", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.es", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.fr", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||
|
||||
// Amazon Web Services
|
||||
new KnownUsernameField("signin.aws.amazon.com", new (string, string)[] { ("signin", "resolving_input") }),
|
||||
|
||||
// Atlassian
|
||||
new KnownUsernameField("id.atlassian.com", new (string, string)[] { ("login", "username") }),
|
||||
|
||||
/*
|
||||
* B
|
||||
*/
|
||||
|
||||
// Bitly ——— enterprise users.
|
||||
new KnownUsernameField("bitly.com", new (string, string)[] { ("/sso/url_slug", "url_slug") }),
|
||||
|
||||
/*
|
||||
* E
|
||||
*/
|
||||
|
||||
// eBay ——— 1st = traditional access / 2nd = direct access (i.e. https://signin.ebay.tld/).
|
||||
new KnownUsernameField("signin.befr.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.benl.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.cafr.ebay.ca", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.at", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.ca", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.ch", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.co.uk", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.com", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.com.au", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.com.hk", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.com.my", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.com.sg", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.de", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.es", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.fr", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.ie", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.in", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.it", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.nl", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.ph", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
new KnownUsernameField("signin.ebay.pl", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
||||
|
||||
/*
|
||||
* G
|
||||
*/
|
||||
|
||||
// Google ——— 1st = used in most cases (v2) / 2nd = used in some cases (v1).
|
||||
new KnownUsernameField("accounts.google.com", new (string, string)[] { ("identifier", "identifierId"), ("ServiceLogin", "Email") }),
|
||||
|
||||
/*
|
||||
* P
|
||||
*/
|
||||
|
||||
// PayPal ——— 1st = traditional access / 2nd = access using OAuth.
|
||||
new KnownUsernameField("paypal.com", new (string, string)[] { ("signin", "email"), ("contains:/connect/", "email") }),
|
||||
|
||||
/*
|
||||
* T
|
||||
*/
|
||||
|
||||
// Tumblr ——— despite "signup" in its ID, it's the login field (the website offers registration if the account doesn't exist).
|
||||
new KnownUsernameField("tumblr.com", new (string, string)[] { ("login", "signup_determine_email") }),
|
||||
|
||||
/*
|
||||
* Y
|
||||
*/
|
||||
|
||||
// Yandex
|
||||
new KnownUsernameField("passport.yandex.az", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.by", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.co.il", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.com", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.com.am", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.com.ge", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.com.tr", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.ee", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.fi", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.fr", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.kg", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.kz", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.lt", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.lv", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.md", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.pl", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.ru", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.tj", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.tm", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.ua", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
new KnownUsernameField("passport.yandex.uz", new (string, string)[] { ("auth", "passp-field-login") }),
|
||||
|
||||
/**************************************************************************************
|
||||
* SECTION B ——— Top 100 worldwide
|
||||
*************************************************************************************/
|
||||
|
||||
// As of July 2020, all entries that needed to be added from
|
||||
// Top 100 (SimilarWeb, 2019) and Top 50 (Alexa Internet, 2020)
|
||||
// matched section A.
|
||||
//
|
||||
// Therefore, no entry currently.
|
||||
|
||||
/**************************************************************************************
|
||||
* SECTION C ——— Top 20 for selected countries
|
||||
*************************************************************************************/
|
||||
|
||||
// REM.: For these selected countries, the Top 20 (SimilarWeb, 2020)
|
||||
// and the Top 20 (Alexa Internet, 2020) are covered.
|
||||
// Mobile and desktop versions supported.
|
||||
//
|
||||
// Could not be added, however:
|
||||
// web sites/applications that don't use an "id" attribute for their login field.
|
||||
|
||||
/*
|
||||
* Japan
|
||||
*/
|
||||
|
||||
// NTT DOCOMO ——— mainly used for "My docomo".
|
||||
new KnownUsernameField("cfg.smt.docomo.ne.jp", new (string, string)[] { ("contains:/auth/", "Di_Uid") }),
|
||||
new KnownUsernameField("id.smt.docomo.ne.jp", new (string, string)[] { ("contains:/cgi7/", "Di_Uid") }),
|
||||
|
||||
/**************************************************************************************
|
||||
* SECTION D ——— Miscellaneous
|
||||
*************************************************************************************/
|
||||
|
||||
/*
|
||||
* Various entries ——— Following user requests, etc.
|
||||
*/
|
||||
|
||||
// No entry, currently.
|
||||
|
||||
/**************************************************************************************
|
||||
* SECTION Z ——— Special forms
|
||||
*
|
||||
* Despite "user ID + password" fields both visible, detection rules required.
|
||||
*************************************************************************************/
|
||||
|
||||
/*
|
||||
* Main
|
||||
*/
|
||||
|
||||
// No entry, currently.
|
||||
|
||||
/*
|
||||
* Test/example purposes only
|
||||
*/
|
||||
|
||||
// GitHub ——— VERY special case (signup form, just to test the proper functioning of special forms).
|
||||
new KnownUsernameField("github.com", new (string, string)[] { ("", "user[login]-footer") }),
|
||||
}.ToDictionary(n => n.UriAuthority);
|
||||
|
||||
public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e)
|
||||
{
|
||||
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
|
||||
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
|
||||
foreach(var node in testNodesData)
|
||||
foreach (var node in testNodesData)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Node: {0} = {1}", node.id, node.text);
|
||||
}
|
||||
@@ -104,49 +345,60 @@ namespace Bit.Droid.Accessibility
|
||||
public static string GetUri(AccessibilityNodeInfo root)
|
||||
{
|
||||
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
|
||||
if(SupportedBrowsers.ContainsKey(root.PackageName))
|
||||
if (SupportedBrowsers.ContainsKey(root.PackageName))
|
||||
{
|
||||
var browser = SupportedBrowsers[root.PackageName];
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||
$"{root.PackageName}:id/{browser.UriViewId}").FirstOrDefault();
|
||||
if(addressNode != null)
|
||||
AccessibilityNodeInfo addressNode = null;
|
||||
foreach (var uriViewId in browser.UriViewId.Split(","))
|
||||
{
|
||||
addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||
$"{root.PackageName}:id/{uriViewId}").FirstOrDefault();
|
||||
if (addressNode != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (addressNode != null)
|
||||
{
|
||||
uri = ExtractUri(uri, addressNode, browser);
|
||||
addressNode.Dispose();
|
||||
addressNode.Recycle();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return null to prevent overwriting notification pendingIntent uri with browser packageName
|
||||
// (we login to pages, not browsers)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
public static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
||||
private static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
||||
{
|
||||
if(addressNode?.Text == null)
|
||||
if (addressNode?.Text == null)
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
if(addressNode.Text == null)
|
||||
if (addressNode.Text == null)
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
uri = browser.GetUriFunction(addressNode.Text)?.Trim();
|
||||
if(uri != null && uri.Contains("."))
|
||||
if (uri != null && uri.Contains("."))
|
||||
{
|
||||
if(!uri.Contains("://") && !uri.Contains(" "))
|
||||
var hasHttpProtocol = uri.StartsWith("http://") || uri.StartsWith("https://");
|
||||
if (!hasHttpProtocol && uri.Contains("."))
|
||||
{
|
||||
uri = string.Concat("http://", uri);
|
||||
}
|
||||
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
|
||||
{
|
||||
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
|
||||
if(parts.Length > 1)
|
||||
if (Uri.TryCreate("https://" + uri, UriKind.Absolute, out var _))
|
||||
{
|
||||
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
||||
if(urlPart != null)
|
||||
{
|
||||
uri = urlPart.Trim();
|
||||
}
|
||||
return string.Concat("https://", uri);
|
||||
}
|
||||
}
|
||||
if (Uri.TryCreate(uri, UriKind.Absolute, out var _))
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
@@ -156,11 +408,11 @@ namespace Bit.Droid.Accessibility
|
||||
/// </summary>
|
||||
public static bool NeedToAutofill(Credentials credentials, string currentUriString)
|
||||
{
|
||||
if(credentials == null)
|
||||
if (credentials == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) &&
|
||||
if (Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) &&
|
||||
Uri.TryCreate(currentUriString, UriKind.Absolute, out Uri currentUri))
|
||||
{
|
||||
return lastUri.Host == currentUri.Host;
|
||||
@@ -173,12 +425,11 @@ namespace Bit.Droid.Accessibility
|
||||
return n?.ClassName?.Contains("EditText") ?? false;
|
||||
}
|
||||
|
||||
|
||||
public static void FillCredentials(AccessibilityNodeInfo usernameNode,
|
||||
IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||
{
|
||||
FillEditText(usernameNode, LastCredentials?.Username);
|
||||
foreach(var n in passwordNodes)
|
||||
foreach (var n in passwordNodes)
|
||||
{
|
||||
FillEditText(n, LastCredentials?.Password);
|
||||
}
|
||||
@@ -186,7 +437,7 @@ namespace Bit.Droid.Accessibility
|
||||
|
||||
public static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
||||
{
|
||||
if(editTextNode == null || value == null)
|
||||
if (editTextNode == null || value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -199,31 +450,35 @@ namespace Bit.Droid.Accessibility
|
||||
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null,
|
||||
int recursionDepth = 0)
|
||||
{
|
||||
if(nodes == null)
|
||||
if (nodes == null)
|
||||
{
|
||||
nodes = new NodeList();
|
||||
}
|
||||
var dispose = disposeIfUnused;
|
||||
if(n != null && recursionDepth < 50)
|
||||
if (n != null && recursionDepth < 100)
|
||||
{
|
||||
var add = n.WindowId == e.WindowId &&
|
||||
!(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) &&
|
||||
condition(n);
|
||||
if(add)
|
||||
if (add)
|
||||
{
|
||||
dispose = false;
|
||||
nodes.Add(n);
|
||||
}
|
||||
|
||||
for(var i = 0; i < n.ChildCount; i++)
|
||||
for (var i = 0; i < n.ChildCount; i++)
|
||||
{
|
||||
var childNode = n.GetChild(i);
|
||||
if(i > 100)
|
||||
if (childNode == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (i > 100)
|
||||
{
|
||||
Android.Util.Log.Info(BitwardenTag, "Too many child iterations.");
|
||||
break;
|
||||
}
|
||||
else if(childNode.GetHashCode() == n.GetHashCode())
|
||||
else if (childNode.GetHashCode() == n.GetHashCode())
|
||||
{
|
||||
Android.Util.Log.Info(BitwardenTag, "Child node is the same as parent for some reason.");
|
||||
}
|
||||
@@ -233,26 +488,401 @@ namespace Bit.Droid.Accessibility
|
||||
}
|
||||
}
|
||||
}
|
||||
if(dispose)
|
||||
if (dispose)
|
||||
{
|
||||
n?.Recycle();
|
||||
n?.Dispose();
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public static void GetNodesAndFill(AccessibilityNodeInfo root, AccessibilityEvent e,
|
||||
IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||
public static AccessibilityNodeInfo GetUsernameEditText(string uriString,
|
||||
IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||
var usernameEditText = GetUsernameEditText(allEditTexts);
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
allEditTexts.Dispose();
|
||||
usernameEditText = null;
|
||||
string uriAuthority = null;
|
||||
string uriKey = null;
|
||||
string uriLocalPath = null;
|
||||
if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
|
||||
{
|
||||
uriAuthority = uri.Authority;
|
||||
uriKey = uriAuthority.StartsWith("www.", StringComparison.Ordinal) ? uriAuthority.Substring(4) : uriAuthority;
|
||||
uriLocalPath = uri.LocalPath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(uriKey))
|
||||
{
|
||||
// Uncomment this to log values necessary for username field discovery
|
||||
// foreach (var editText in allEditTexts)
|
||||
// {
|
||||
// System.Diagnostics.Debug.WriteLine(">>> uriKey: {0}, uriLocalPath: {1}, viewId: {2}", uriKey,
|
||||
// uriLocalPath, editText.ViewIdResourceName);
|
||||
// }
|
||||
|
||||
if (KnownUsernameFields.ContainsKey(uriKey))
|
||||
{
|
||||
var usernameField = KnownUsernameFields[uriKey];
|
||||
(string UriPathWanted, string UsernameViewId)[] accessOptions = usernameField.AccessOptions;
|
||||
|
||||
for (int i = 0; i < accessOptions.Length; i++)
|
||||
{
|
||||
string curUriPathWanted = accessOptions[i].UriPathWanted;
|
||||
string curUsernameViewId = accessOptions[i].UsernameViewId;
|
||||
bool uriLocalPathMatches = false;
|
||||
|
||||
// Case-sensitive comparison
|
||||
if (curUriPathWanted.StartsWith("startswith:", StringComparison.Ordinal))
|
||||
{
|
||||
curUriPathWanted = curUriPathWanted.Substring(11);
|
||||
uriLocalPathMatches = uriLocalPath.StartsWith(curUriPathWanted, StringComparison.Ordinal);
|
||||
}
|
||||
else if (curUriPathWanted.StartsWith("contains:", StringComparison.Ordinal))
|
||||
{
|
||||
curUriPathWanted = curUriPathWanted.Substring(9);
|
||||
uriLocalPathMatches = uriLocalPath.Contains(curUriPathWanted, StringComparison.Ordinal);
|
||||
}
|
||||
else if (curUriPathWanted.StartsWith("endswith:", StringComparison.Ordinal))
|
||||
{
|
||||
curUriPathWanted = curUriPathWanted.Substring(9);
|
||||
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
// Case-insensitive comparison
|
||||
else if (curUriPathWanted.StartsWith("istartswith:", StringComparison.Ordinal))
|
||||
{
|
||||
curUriPathWanted = curUriPathWanted.Substring(12);
|
||||
uriLocalPathMatches = uriLocalPath.StartsWith(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (curUriPathWanted.StartsWith("icontains:", StringComparison.Ordinal))
|
||||
{
|
||||
curUriPathWanted = curUriPathWanted.Substring(10);
|
||||
uriLocalPathMatches = uriLocalPath.Contains(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (curUriPathWanted.StartsWith("iendswith:", StringComparison.Ordinal))
|
||||
{
|
||||
curUriPathWanted = curUriPathWanted.Substring(10);
|
||||
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// Default type of comparison
|
||||
else
|
||||
{
|
||||
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
if (uriLocalPathMatches)
|
||||
{
|
||||
foreach (var editText in allEditTexts)
|
||||
{
|
||||
foreach (var usernameViewId in curUsernameViewId.Split(","))
|
||||
{
|
||||
if (usernameViewId == editText.ViewIdResourceName)
|
||||
{
|
||||
return editText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no match found, attempt to establish username field based on password field
|
||||
return GetUsernameEditTextIfPasswordExists(allEditTexts);
|
||||
}
|
||||
|
||||
private static AccessibilityNodeInfo GetUsernameEditTextIfPasswordExists(
|
||||
IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
||||
{
|
||||
AccessibilityNodeInfo previousEditText = null;
|
||||
foreach (var editText in allEditTexts)
|
||||
{
|
||||
if (editText.Password)
|
||||
{
|
||||
return previousEditText;
|
||||
}
|
||||
previousEditText = editText;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static AccessibilityNodeInfo GetUsernameEditText(IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
||||
public static bool IsUsernameEditText(AccessibilityNodeInfo root, AccessibilityEvent e)
|
||||
{
|
||||
return allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||
var uriString = GetUri(root);
|
||||
var usernameEditText = GetUsernameEditText(uriString, allEditTexts);
|
||||
|
||||
var isUsernameEditText = false;
|
||||
if (usernameEditText != null)
|
||||
{
|
||||
isUsernameEditText = IsSameNode(usernameEditText, e.Source);
|
||||
}
|
||||
allEditTexts.Dispose();
|
||||
|
||||
return isUsernameEditText;
|
||||
}
|
||||
|
||||
public static bool IsSameNode(AccessibilityNodeInfo node1, AccessibilityNodeInfo node2)
|
||||
{
|
||||
if (node1 != null && node2 != null)
|
||||
{
|
||||
return node1.Equals(node2) || node1.GetHashCode() == node2.GetHashCode();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool OverlayPermitted()
|
||||
{
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||
{
|
||||
if (Settings.CanDrawOverlays(Application.Context))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var appOpsMgr = (AppOpsManager)Application.Context.GetSystemService(Context.AppOpsService);
|
||||
var mode = appOpsMgr.CheckOpNoThrow("android:system_alert_window", Process.MyUid(),
|
||||
Application.Context.PackageName);
|
||||
if (mode == AppOpsManagerMode.Allowed || mode == AppOpsManagerMode.Ignored)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var wm = Application.Context.GetSystemService(Context.WindowService)
|
||||
.JavaCast<IWindowManager>();
|
||||
if (wm == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var testView = new View(Application.Context);
|
||||
var layoutParams = GetOverlayLayoutParams();
|
||||
wm.AddView(testView, layoutParams);
|
||||
wm.RemoveView(testView);
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// older android versions are always true
|
||||
return true;
|
||||
}
|
||||
|
||||
public static LinearLayout GetOverlayView(Context context)
|
||||
{
|
||||
var inflater = (LayoutInflater)context.GetSystemService(Context.LayoutInflaterService);
|
||||
var view = (LinearLayout)inflater.Inflate(Resource.Layout.autofill_listitem, null);
|
||||
var text1 = (TextView)view.FindViewById(Resource.Id.text1);
|
||||
var text2 = (TextView)view.FindViewById(Resource.Id.text2);
|
||||
var icon = (ImageView)view.FindViewById(Resource.Id.icon);
|
||||
text1.Text = AppResources.AutofillWithBitwarden;
|
||||
text2.Text = AppResources.GoToMyVault;
|
||||
icon.SetImageResource(Resource.Drawable.shield);
|
||||
return view;
|
||||
}
|
||||
|
||||
public static WindowManagerLayoutParams GetOverlayLayoutParams()
|
||||
{
|
||||
WindowManagerTypes windowManagerType;
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
windowManagerType = WindowManagerTypes.ApplicationOverlay;
|
||||
}
|
||||
else
|
||||
{
|
||||
windowManagerType = WindowManagerTypes.Phone;
|
||||
}
|
||||
|
||||
var layoutParams = new WindowManagerLayoutParams(
|
||||
ViewGroup.LayoutParams.WrapContent,
|
||||
ViewGroup.LayoutParams.WrapContent,
|
||||
windowManagerType,
|
||||
WindowManagerFlags.NotFocusable | WindowManagerFlags.NotTouchModal,
|
||||
Format.Transparent);
|
||||
layoutParams.Gravity = GravityFlags.Top | GravityFlags.Left;
|
||||
|
||||
return layoutParams;
|
||||
}
|
||||
|
||||
public static Point GetOverlayAnchorPosition(AccessibilityService service, AccessibilityNodeInfo anchorView,
|
||||
int overlayViewHeight, bool isOverlayAboveAnchor)
|
||||
{
|
||||
var anchorViewRect = new Rect();
|
||||
anchorView.GetBoundsInScreen(anchorViewRect);
|
||||
var anchorViewX = anchorViewRect.Left;
|
||||
var anchorViewY = isOverlayAboveAnchor ? anchorViewRect.Top : anchorViewRect.Bottom;
|
||||
anchorViewRect.Dispose();
|
||||
|
||||
if (isOverlayAboveAnchor)
|
||||
{
|
||||
anchorViewY -= overlayViewHeight;
|
||||
}
|
||||
anchorViewY -= GetStatusBarHeight(service);
|
||||
|
||||
return new Point(anchorViewX, anchorViewY);
|
||||
}
|
||||
|
||||
public static Point GetOverlayAnchorPosition(AccessibilityService service, AccessibilityNodeInfo anchorNode,
|
||||
AccessibilityNodeInfo root, IEnumerable<AccessibilityWindowInfo> windows, int overlayViewHeight,
|
||||
bool isOverlayAboveAnchor)
|
||||
{
|
||||
Point point = null;
|
||||
if (anchorNode != null)
|
||||
{
|
||||
// Update node's info since this is still a reference from an older event
|
||||
anchorNode.Refresh();
|
||||
if (!anchorNode.VisibleToUser)
|
||||
{
|
||||
return new Point(-1, -1);
|
||||
}
|
||||
if (!anchorNode.Focused)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// node.VisibleToUser doesn't always give us exactly what we want, so attempt to tighten up the range
|
||||
// of visibility
|
||||
var inputMethodHeight = 0;
|
||||
if (windows != null)
|
||||
{
|
||||
if (IsStatusBarExpanded(windows))
|
||||
{
|
||||
return new Point(-1, -1);
|
||||
}
|
||||
inputMethodHeight = GetInputMethodHeight(windows);
|
||||
}
|
||||
var minY = 0;
|
||||
var rootNodeHeight = GetNodeHeight(root);
|
||||
if (rootNodeHeight == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var maxY = rootNodeHeight - GetNavigationBarHeight(service) - GetStatusBarHeight(service) -
|
||||
inputMethodHeight;
|
||||
|
||||
point = GetOverlayAnchorPosition(service, anchorNode, overlayViewHeight, isOverlayAboveAnchor);
|
||||
if (point.Y < minY)
|
||||
{
|
||||
if (isOverlayAboveAnchor)
|
||||
{
|
||||
// view nearing bounds, anchor to bottom
|
||||
point.X = -1;
|
||||
point.Y = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// view out of bounds, hide overlay
|
||||
point.X = -1;
|
||||
point.Y = -1;
|
||||
}
|
||||
}
|
||||
else if (point.Y > (maxY - overlayViewHeight))
|
||||
{
|
||||
if (isOverlayAboveAnchor)
|
||||
{
|
||||
// view out of bounds, hide overlay
|
||||
point.X = -1;
|
||||
point.Y = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// view nearing bounds, anchor to top
|
||||
point.X = 0;
|
||||
point.Y = -1;
|
||||
}
|
||||
}
|
||||
else if (isOverlayAboveAnchor && point.Y < (maxY - (overlayViewHeight * 2) - GetNodeHeight(anchorNode)))
|
||||
{
|
||||
// This else block forces the overlay to return to bottom alignment as soon as space is available
|
||||
// below the anchor view. Removing this will change the behavior to wait until there isn't enough
|
||||
// space above the anchor view before returning to bottom alignment.
|
||||
point.X = -1;
|
||||
point.Y = 0;
|
||||
}
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
public static bool IsStatusBarExpanded(IEnumerable<AccessibilityWindowInfo> windows)
|
||||
{
|
||||
if (windows != null && windows.Any())
|
||||
{
|
||||
var isSystemWindowsOnly = true;
|
||||
foreach (var window in windows)
|
||||
{
|
||||
if (window.Type != AccessibilityWindowType.System)
|
||||
{
|
||||
isSystemWindowsOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isSystemWindowsOnly;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int GetInputMethodHeight(IEnumerable<AccessibilityWindowInfo> windows)
|
||||
{
|
||||
var inputMethodWindowHeight = 0;
|
||||
if (windows != null)
|
||||
{
|
||||
foreach (var window in windows)
|
||||
{
|
||||
if (window.Type == AccessibilityWindowType.InputMethod)
|
||||
{
|
||||
var windowRect = new Rect();
|
||||
window.GetBoundsInScreen(windowRect);
|
||||
inputMethodWindowHeight = windowRect.Height();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return inputMethodWindowHeight;
|
||||
}
|
||||
|
||||
public static bool IsAutofillServicePromptVisible(IEnumerable<AccessibilityWindowInfo> windows)
|
||||
{
|
||||
// Autofill framework not available until API 26
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
return windows?.Any(w => w.Title?.ToLower().Contains("autofill") ?? false) ?? false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int GetNodeHeight(AccessibilityNodeInfo node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
var nodeRect = new Rect();
|
||||
node.GetBoundsInScreen(nodeRect);
|
||||
var nodeRectHeight = nodeRect.Height();
|
||||
nodeRect.Dispose();
|
||||
return nodeRectHeight;
|
||||
}
|
||||
|
||||
private static int GetStatusBarHeight(AccessibilityService service)
|
||||
{
|
||||
return GetSystemResourceDimenPx(service, "status_bar_height");
|
||||
}
|
||||
|
||||
private static int GetNavigationBarHeight(AccessibilityService service)
|
||||
{
|
||||
return GetSystemResourceDimenPx(service, "navigation_bar_height");
|
||||
}
|
||||
|
||||
private static int GetSystemResourceDimenPx(AccessibilityService service, string resName)
|
||||
{
|
||||
var resourceId = service.Resources.GetIdentifier(resName, "dimen", "android");
|
||||
if (resourceId > 0)
|
||||
{
|
||||
return service.Resources.GetDimensionPixelSize(resourceId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -6,7 +6,9 @@ using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Views.Accessibility;
|
||||
using Android.Widget;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
@@ -20,149 +22,142 @@ namespace Bit.Droid.Accessibility
|
||||
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
|
||||
public class AccessibilityService : Android.AccessibilityServices.AccessibilityService
|
||||
{
|
||||
private NotificationChannel _notificationChannel;
|
||||
|
||||
private const int AutoFillNotificationId = 34573;
|
||||
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
||||
private const string BitwardenWebsite = "vault.bitwarden.com";
|
||||
|
||||
private IStorageService _storageService;
|
||||
private bool _settingAutofillPasswordField;
|
||||
private bool _settingAutofillPersistNotification;
|
||||
private IBroadcasterService _broadcasterService;
|
||||
private DateTime? _lastSettingsReload = null;
|
||||
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
|
||||
private long _lastNotificationTime = 0;
|
||||
private string _lastNotificationUri = null;
|
||||
private HashSet<string> _blacklistedUris;
|
||||
private AccessibilityNodeInfo _anchorNode = null;
|
||||
private int _lastAnchorX = 0;
|
||||
private int _lastAnchorY = 0;
|
||||
private bool _isOverlayAboveAnchor = false;
|
||||
private static bool _overlayAnchorObserverRunning = false;
|
||||
private IWindowManager _windowManager = null;
|
||||
private LinearLayout _overlayView = null;
|
||||
private int _overlayViewHeight = 0;
|
||||
private long _lastAutoFillTime = 0;
|
||||
private Java.Lang.Runnable _overlayAnchorObserverRunnable = null;
|
||||
private Handler _handler = new Handler(Looper.MainLooper);
|
||||
|
||||
private HashSet<string> _launcherPackageNames = null;
|
||||
private DateTime? _lastLauncherSetBuilt = null;
|
||||
private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1);
|
||||
|
||||
public override void OnCreate()
|
||||
{
|
||||
base.OnCreate();
|
||||
LoadServices();
|
||||
var settingsTask = LoadSettingsAsync();
|
||||
_broadcasterService.Subscribe(nameof(AccessibilityService), (message) =>
|
||||
{
|
||||
if (message.Command == "OnAutofillTileClick")
|
||||
{
|
||||
var runnable = new Java.Lang.Runnable(OnAutofillTileClick);
|
||||
_handler.PostDelayed(runnable, 250);
|
||||
}
|
||||
});
|
||||
AccessibilityHelpers.IsAccessibilityBroadcastReady = true;
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
AccessibilityHelpers.IsAccessibilityBroadcastReady = false;
|
||||
_broadcasterService.Unsubscribe(nameof(AccessibilityService));
|
||||
}
|
||||
|
||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var powerManager = GetSystemService(PowerService) as PowerManager;
|
||||
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
|
||||
if (Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if(Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
|
||||
else if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(SkipPackage(e?.PackageName))
|
||||
if (SkipPackage(e?.PackageName))
|
||||
{
|
||||
if (e?.PackageName != "com.android.systemui")
|
||||
{
|
||||
CancelOverlayPrompt();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var root = RootInActiveWindow;
|
||||
if(root == null || root.PackageName != e.PackageName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// AccessibilityHelpers.PrintTestData(RootInActiveWindow, e);
|
||||
|
||||
// AccessibilityHelpers.PrintTestData(root, e);
|
||||
LoadServices();
|
||||
var settingsTask = LoadSettingsAsync();
|
||||
AccessibilityNodeInfo root = null;
|
||||
|
||||
var notificationManager = GetSystemService(NotificationService) as NotificationManager;
|
||||
var cancelNotification = true;
|
||||
|
||||
switch(e.EventType)
|
||||
switch (e.EventType)
|
||||
{
|
||||
case EventTypes.ViewFocused:
|
||||
if(e.Source == null || !e.Source.Password || !_settingAutofillPasswordField)
|
||||
case EventTypes.ViewClicked:
|
||||
if (e.Source == null || e.PackageName == BitwardenPackage)
|
||||
{
|
||||
CancelOverlayPrompt();
|
||||
break;
|
||||
}
|
||||
|
||||
root = RootInActiveWindow;
|
||||
if (root == null || root.PackageName != e.PackageName)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
|
||||
if (!(e.Source?.Password ?? false) && !AccessibilityHelpers.IsUsernameEditText(root, e))
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
CancelOverlayPrompt();
|
||||
break;
|
||||
}
|
||||
if(ScanAndAutofill(root, e, notificationManager, cancelNotification))
|
||||
if (ScanAndAutofill(root, e))
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
CancelOverlayPrompt();
|
||||
}
|
||||
else
|
||||
{
|
||||
OverlayPromptToAutofill(root, e);
|
||||
}
|
||||
break;
|
||||
case EventTypes.WindowContentChanged:
|
||||
case EventTypes.WindowStateChanged:
|
||||
if(_settingAutofillPasswordField && e.Source.Password)
|
||||
if (AccessibilityHelpers.LastCredentials == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if(_settingAutofillPasswordField && AccessibilityHelpers.LastCredentials == null)
|
||||
if (e.PackageName == BitwardenPackage)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(_lastNotificationUri))
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
break;
|
||||
}
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
if(uri != _lastNotificationUri)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
}
|
||||
else if(uri.StartsWith(Constants.AndroidAppProtocol))
|
||||
{
|
||||
CancelNotification(notificationManager, 30000);
|
||||
}
|
||||
CancelOverlayPrompt();
|
||||
break;
|
||||
}
|
||||
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
root = RootInActiveWindow;
|
||||
if (root == null || root.PackageName != e.PackageName)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
break;
|
||||
}
|
||||
|
||||
if(_settingAutofillPersistNotification)
|
||||
if (ScanAndAutofill(root, e))
|
||||
{
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||
{
|
||||
var needToFill = AccessibilityHelpers.NeedToAutofill(
|
||||
AccessibilityHelpers.LastCredentials, uri);
|
||||
if(needToFill)
|
||||
{
|
||||
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e,
|
||||
n => n.Password, false);
|
||||
needToFill = passwordNodes.Any();
|
||||
if(needToFill)
|
||||
{
|
||||
AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes);
|
||||
}
|
||||
passwordNodes.Dispose();
|
||||
}
|
||||
if(!needToFill)
|
||||
{
|
||||
NotifyToAutofill(uri, notificationManager);
|
||||
cancelNotification = false;
|
||||
}
|
||||
}
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
cancelNotification = ScanAndAutofill(root, e, notificationManager, cancelNotification);
|
||||
}
|
||||
|
||||
if(cancelNotification)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
CancelOverlayPrompt();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
notificationManager?.Dispose();
|
||||
root.Dispose();
|
||||
e.Dispose();
|
||||
}
|
||||
// Suppress exceptions so that service doesn't crash.
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
@@ -170,28 +165,27 @@ namespace Bit.Droid.Accessibility
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e,
|
||||
NotificationManager notificationManager, bool cancelNotification)
|
||||
public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e)
|
||||
{
|
||||
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false);
|
||||
if(passwordNodes.Count > 0)
|
||||
var filled = false;
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
if (uri != null && !uri.Contains(BitwardenWebsite) &&
|
||||
AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri))
|
||||
{
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||
var allEditTexts = AccessibilityHelpers.GetWindowNodes(root, e, n => AccessibilityHelpers.EditText(n), false);
|
||||
var usernameEditText = AccessibilityHelpers.GetUsernameEditText(uri, allEditTexts);
|
||||
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false);
|
||||
if (usernameEditText != null || passwordNodes.Count > 0)
|
||||
{
|
||||
if(AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri))
|
||||
{
|
||||
AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyToAutofill(uri, notificationManager);
|
||||
cancelNotification = false;
|
||||
}
|
||||
AccessibilityHelpers.FillCredentials(usernameEditText, passwordNodes);
|
||||
filled = true;
|
||||
_lastAutoFillTime = Java.Lang.JavaSystem.CurrentTimeMillis();
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
}
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
allEditTexts.Dispose();
|
||||
passwordNodes.Dispose();
|
||||
}
|
||||
else if(AccessibilityHelpers.LastCredentials != null)
|
||||
if (AccessibilityHelpers.LastCredentials != null)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
@@ -199,81 +193,243 @@ namespace Bit.Droid.Accessibility
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
});
|
||||
}
|
||||
passwordNodes.Dispose();
|
||||
return cancelNotification;
|
||||
return filled;
|
||||
}
|
||||
|
||||
private void OnAutofillTileClick()
|
||||
{
|
||||
CancelOverlayPrompt();
|
||||
|
||||
var root = RootInActiveWindow;
|
||||
if (root != null && root.PackageName != BitwardenPackage &&
|
||||
root.PackageName != AccessibilityHelpers.SystemUiPackage &&
|
||||
!SkipPackage(root.PackageName))
|
||||
{
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
if (!string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
var intent = new Intent(this, typeof(AccessibilityActivity));
|
||||
intent.PutExtra("uri", uri);
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
StartActivity(intent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Toast.MakeText(this, AppResources.AutofillTileUriNotFound, ToastLength.Long).Show();
|
||||
}
|
||||
|
||||
public void CancelNotification(NotificationManager notificationManager, long limit = 250)
|
||||
private void CancelOverlayPrompt()
|
||||
{
|
||||
if(Java.Lang.JavaSystem.CurrentTimeMillis() - _lastNotificationTime < limit)
|
||||
_overlayAnchorObserverRunning = false;
|
||||
|
||||
if (_windowManager != null && _overlayView != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_windowManager.RemoveViewImmediate(_overlayView);
|
||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Removed");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
_overlayView = null;
|
||||
_lastAnchorX = 0;
|
||||
_lastAnchorY = 0;
|
||||
_isOverlayAboveAnchor = false;
|
||||
|
||||
if (_anchorNode != null)
|
||||
{
|
||||
_anchorNode.Recycle();
|
||||
_anchorNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OverlayPromptToAutofill(AccessibilityNodeInfo root, AccessibilityEvent e)
|
||||
{
|
||||
if (Java.Lang.JavaSystem.CurrentTimeMillis() - _lastAutoFillTime < 1000 ||
|
||||
AccessibilityHelpers.IsAutofillServicePromptVisible(Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastNotificationUri = null;
|
||||
notificationManager?.Cancel(AutoFillNotificationId);
|
||||
}
|
||||
|
||||
if (!AccessibilityHelpers.OverlayPermitted())
|
||||
{
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
|
||||
{
|
||||
// The user has the option of only using the autofill tile and leaving the overlay permission
|
||||
// disabled, so only show this toast if they're using accessibility without overlay permission on
|
||||
// a version of Android without quick-action tile support
|
||||
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
|
||||
Toast.MakeText(this, AppResources.AccessibilityDrawOverPermissionAlert, ToastLength.Long).Show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private void NotifyToAutofill(string uri, NotificationManager notificationManager)
|
||||
{
|
||||
if(notificationManager == null || string.IsNullOrWhiteSpace(uri))
|
||||
if (_overlayView != null || _anchorNode != null || _overlayAnchorObserverRunning)
|
||||
{
|
||||
CancelOverlayPrompt();
|
||||
}
|
||||
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
var fillable = !string.IsNullOrWhiteSpace(uri);
|
||||
if (fillable)
|
||||
{
|
||||
if (_blacklistedUris != null && _blacklistedUris.Any())
|
||||
{
|
||||
if (Uri.TryCreate(uri, UriKind.Absolute, out var parsedUri) && parsedUri.Scheme.StartsWith("http"))
|
||||
{
|
||||
fillable = !_blacklistedUris.Contains(
|
||||
string.Format("{0}://{1}", parsedUri.Scheme, parsedUri.Host));
|
||||
}
|
||||
else
|
||||
{
|
||||
fillable = !_blacklistedUris.Contains(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fillable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var now = Java.Lang.JavaSystem.CurrentTimeMillis();
|
||||
var intent = new Intent(this, typeof(AccessibilityActivity));
|
||||
intent.PutExtra("uri", uri);
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.UpdateCurrent);
|
||||
|
||||
var notificationContent = Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch ?
|
||||
AppResources.BitwardenAutofillServiceNotificationContent :
|
||||
AppResources.BitwardenAutofillServiceNotificationContentOld;
|
||||
|
||||
var builder = new Notification.Builder(this);
|
||||
builder.SetSmallIcon(Resource.Drawable.shield)
|
||||
.SetContentTitle(AppResources.BitwardenAutofillService)
|
||||
.SetContentText(notificationContent)
|
||||
.SetTicker(notificationContent)
|
||||
.SetWhen(now)
|
||||
.SetContentIntent(pendingIntent);
|
||||
|
||||
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
|
||||
_overlayView = AccessibilityHelpers.GetOverlayView(this);
|
||||
_overlayView.Measure(View.MeasureSpec.MakeMeasureSpec(0, 0),
|
||||
View.MeasureSpec.MakeMeasureSpec(0, 0));
|
||||
_overlayViewHeight = _overlayView.MeasuredHeight;
|
||||
_overlayView.Click += (sender, eventArgs) =>
|
||||
{
|
||||
builder.SetVisibility(NotificationVisibility.Secret)
|
||||
.SetColor(Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
|
||||
Resource.Color.primary));
|
||||
CancelOverlayPrompt();
|
||||
StartActivity(intent);
|
||||
};
|
||||
|
||||
var layoutParams = AccessibilityHelpers.GetOverlayLayoutParams();
|
||||
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(this, e.Source,
|
||||
_overlayViewHeight, _isOverlayAboveAnchor);
|
||||
layoutParams.X = anchorPosition.X;
|
||||
layoutParams.Y = anchorPosition.Y;
|
||||
|
||||
if (_windowManager == null)
|
||||
{
|
||||
_windowManager = GetSystemService(WindowService).JavaCast<IWindowManager>();
|
||||
}
|
||||
if(Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
|
||||
_anchorNode = e.Source;
|
||||
_lastAnchorX = anchorPosition.X;
|
||||
_lastAnchorY = anchorPosition.Y;
|
||||
|
||||
_windowManager.AddView(_overlayView, layoutParams);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Added at X:{0} Y:{1}",
|
||||
layoutParams.X, layoutParams.Y);
|
||||
|
||||
StartOverlayAnchorObserver();
|
||||
}
|
||||
|
||||
private void StartOverlayAnchorObserver()
|
||||
{
|
||||
if (_overlayAnchorObserverRunning)
|
||||
{
|
||||
if(_notificationChannel == null)
|
||||
return;
|
||||
}
|
||||
|
||||
_overlayAnchorObserverRunning = true;
|
||||
_overlayAnchorObserverRunnable = new Java.Lang.Runnable(() =>
|
||||
{
|
||||
if (_overlayAnchorObserverRunning)
|
||||
{
|
||||
_notificationChannel = new NotificationChannel("bitwarden_autofill_service",
|
||||
AppResources.AutofillService, NotificationImportance.Low);
|
||||
notificationManager.CreateNotificationChannel(_notificationChannel);
|
||||
AdjustOverlayForScroll();
|
||||
_handler.PostDelayed(_overlayAnchorObserverRunnable, 250);
|
||||
}
|
||||
builder.SetChannelId(_notificationChannel.Id);
|
||||
}
|
||||
if(/*Build.VERSION.SdkInt <= BuildVersionCodes.N && */_settingAutofillPersistNotification)
|
||||
});
|
||||
|
||||
_handler.PostDelayed(_overlayAnchorObserverRunnable, 250);
|
||||
}
|
||||
|
||||
private void AdjustOverlayForScroll()
|
||||
{
|
||||
if (_overlayView == null || _anchorNode == null ||
|
||||
AccessibilityHelpers.IsAutofillServicePromptVisible(Windows))
|
||||
{
|
||||
builder.SetPriority(-2);
|
||||
CancelOverlayPrompt();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastNotificationTime = now;
|
||||
_lastNotificationUri = uri;
|
||||
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||
builder.Dispose();
|
||||
var root = RootInActiveWindow;
|
||||
IEnumerable<AccessibilityWindowInfo> windows = null;
|
||||
if (Build.VERSION.SdkInt > BuildVersionCodes.Kitkat)
|
||||
{
|
||||
windows = Windows;
|
||||
}
|
||||
|
||||
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(this, _anchorNode, root,
|
||||
windows, _overlayViewHeight, _isOverlayAboveAnchor);
|
||||
if (anchorPosition == null)
|
||||
{
|
||||
CancelOverlayPrompt();
|
||||
return;
|
||||
}
|
||||
else if (anchorPosition.X == -1 && anchorPosition.Y == -1)
|
||||
{
|
||||
if (_overlayView.Visibility != ViewStates.Gone)
|
||||
{
|
||||
_overlayView.Visibility = ViewStates.Gone;
|
||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Hidden");
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (anchorPosition.X == -1)
|
||||
{
|
||||
_isOverlayAboveAnchor = false;
|
||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Below Anchor");
|
||||
return;
|
||||
}
|
||||
else if (anchorPosition.Y == -1)
|
||||
{
|
||||
_isOverlayAboveAnchor = true;
|
||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Above Anchor");
|
||||
return;
|
||||
}
|
||||
else if (anchorPosition.X == _lastAnchorX && anchorPosition.Y == _lastAnchorY)
|
||||
{
|
||||
if (_overlayView.Visibility != ViewStates.Visible)
|
||||
{
|
||||
_overlayView.Visibility = ViewStates.Visible;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var layoutParams = AccessibilityHelpers.GetOverlayLayoutParams();
|
||||
layoutParams.X = anchorPosition.X;
|
||||
layoutParams.Y = anchorPosition.Y;
|
||||
|
||||
_lastAnchorX = anchorPosition.X;
|
||||
_lastAnchorY = anchorPosition.Y;
|
||||
|
||||
_windowManager.UpdateViewLayout(_overlayView, layoutParams);
|
||||
|
||||
if (_overlayView.Visibility != ViewStates.Visible)
|
||||
{
|
||||
_overlayView.Visibility = ViewStates.Visible;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Updated to X:{0} Y:{1}",
|
||||
layoutParams.X, layoutParams.Y);
|
||||
}
|
||||
|
||||
private bool SkipPackage(string eventPackageName)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(eventPackageName) ||
|
||||
if (string.IsNullOrWhiteSpace(eventPackageName) ||
|
||||
AccessibilityHelpers.FilteredPackageNames.Contains(eventPackageName) ||
|
||||
eventPackageName.Contains("launcher"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(_launcherPackageNames == null || _lastLauncherSetBuilt == null ||
|
||||
if (_launcherPackageNames == null || _lastLauncherSetBuilt == null ||
|
||||
(DateTime.Now - _lastLauncherSetBuilt.Value) > _rebuildLauncherSpan)
|
||||
{
|
||||
// refresh launcher list every now and then
|
||||
@@ -288,22 +444,29 @@ namespace Bit.Droid.Accessibility
|
||||
|
||||
private void LoadServices()
|
||||
{
|
||||
if(_storageService == null)
|
||||
if (_storageService == null)
|
||||
{
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
}
|
||||
if (_broadcasterService == null)
|
||||
{
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadSettingsAsync()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if(_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
|
||||
if (_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
|
||||
{
|
||||
_lastSettingsReload = now;
|
||||
_settingAutofillPasswordField = await _storageService.GetAsync<bool>(
|
||||
Constants.AccessibilityAutofillPasswordFieldKey);
|
||||
_settingAutofillPersistNotification = await _storageService.GetAsync<bool>(
|
||||
Constants.AccessibilityAutofillPersistNotificationKey);
|
||||
var uris = await _storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
|
||||
if (uris != null)
|
||||
{
|
||||
_blacklistedUris = new HashSet<string>(uris);
|
||||
}
|
||||
var isAutoFillTileAdded = await _storageService.GetAsync<bool?>(Constants.AutofillTileAdded);
|
||||
AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
src/Android/Accessibility/KnownUsernameField.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
public class KnownUsernameField
|
||||
{
|
||||
public KnownUsernameField(string uriAuthority, (string UriPathWanted, string UsernameViewId)[] accessOptions)
|
||||
{
|
||||
UriAuthority = uriAuthority;
|
||||
AccessOptions = accessOptions;
|
||||
}
|
||||
|
||||
public string UriAuthority { get; set; }
|
||||
public (string UriPathWanted, string UsernameViewId)[] AccessOptions { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,11 @@ namespace Bit.Droid.Accessibility
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var item in this)
|
||||
foreach (var item in this)
|
||||
{
|
||||
item.Recycle();
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
||||
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
||||
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
|
||||
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
|
||||
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
@@ -30,10 +29,10 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
|
||||
<AndroidSupportedAbis />
|
||||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
<AndroidLinkSkip>Xamarin.GooglePlayServices.Base;Xamarin.GooglePlayServices.Basement;Xamarin.GooglePlayServices.Measurement;Xamarin.GooglePlayServices.Gcm;BitwardenAndroid;BitwardenApp;BitwardenCore;Xamarin.Android.Net</AndroidLinkSkip>
|
||||
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
@@ -46,10 +45,8 @@
|
||||
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
|
||||
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
|
||||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
|
||||
<AndroidLinkSkip>Xamarin.GooglePlayServices.Base;Xamarin.GooglePlayServices.Basement;Xamarin.GooglePlayServices.Measurement;Xamarin.GooglePlayServices.Gcm;BitwardenAndroid;BitwardenApp;BitwardenCore;Xamarin.Android.Net</AndroidLinkSkip>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
<AndroidLinkMode>Full</AndroidLinkMode>
|
||||
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'FDroid|AnyCPU'">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
@@ -63,11 +60,9 @@
|
||||
<DefineConstants>FDROID</DefineConstants>
|
||||
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
|
||||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
|
||||
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
<AndroidLinkSkip>Xamarin.GooglePlayServices.Base;Xamarin.GooglePlayServices.Basement;Xamarin.GooglePlayServices.Measurement;Xamarin.GooglePlayServices.Gcm;BitwardenAndroid;BitwardenApp;BitwardenCore;Xamarin.Android.Net</AndroidLinkSkip>
|
||||
<AndroidLinkMode>Full</AndroidLinkMode>
|
||||
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Mono.Android" />
|
||||
@@ -82,21 +77,23 @@
|
||||
<Version>2.1.0.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Portable.BouncyCastle">
|
||||
<Version>1.8.5</Version>
|
||||
<Version>1.8.6.7</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.3-beta01" />
|
||||
<PackageReference Include="Xamarin.Essentials">
|
||||
<Version>1.3.1</Version>
|
||||
<Version>1.5.3.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||
<Version>60.1142.1</Version>
|
||||
<Version>71.1740.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.3" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.3" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v4" Version="28.0.0.3" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="28.0.0.3" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0.3" />
|
||||
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0" />
|
||||
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.1.0" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0" />
|
||||
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0" />
|
||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.1.0" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.6" />
|
||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
|
||||
<Version>60.1142.1</Version>
|
||||
<Version>71.1600.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -106,6 +103,7 @@
|
||||
<Compile Include="Accessibility\AccessibilityService.cs" />
|
||||
<Compile Include="Accessibility\Browser.cs" />
|
||||
<Compile Include="Accessibility\NodeList.cs" />
|
||||
<Compile Include="Accessibility\KnownUsernameField.cs" />
|
||||
<Compile Include="Autofill\AutofillHelpers.cs" />
|
||||
<Compile Include="Autofill\AutofillService.cs" />
|
||||
<Compile Include="Autofill\Field.cs" />
|
||||
@@ -113,16 +111,19 @@
|
||||
<Compile Include="Autofill\FilledItem.cs" />
|
||||
<Compile Include="Autofill\Parser.cs" />
|
||||
<Compile Include="Autofill\SavedItem.cs" />
|
||||
<Compile Include="Effects\FabShadowEffect.cs" />
|
||||
<Compile Include="Effects\FixedSizeEffect.cs" />
|
||||
<Compile Include="Effects\SelectableLabelEffect.cs" />
|
||||
<Compile Include="Effects\TabBarEffect.cs" />
|
||||
<Compile Include="Push\FirebaseInstanceIdService.cs" />
|
||||
<Compile Include="Push\FirebaseMessagingService.cs" />
|
||||
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
|
||||
<Compile Include="Receivers\RestrictionsChangedReceiver.cs" />
|
||||
<Compile Include="Receivers\EventUploadReceiver.cs" />
|
||||
<Compile Include="Receivers\LockAlarmReceiver.cs" />
|
||||
<Compile Include="Receivers\PackageReplacedReceiver.cs" />
|
||||
<Compile Include="Renderers\CipherViewCellRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
|
||||
<Compile Include="Renderers\ExtendedSliderRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomEditorRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomPickerRenderer.cs" />
|
||||
@@ -136,15 +137,16 @@
|
||||
<Compile Include="MainActivity.cs" />
|
||||
<Compile Include="Resources\Resource.designer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\BiometricService.cs" />
|
||||
<Compile Include="Services\CryptoPrimitiveService.cs" />
|
||||
<Compile Include="Services\DeviceActionService.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
<Compile Include="Tiles\AutofillTileService.cs" />
|
||||
<Compile Include="Tiles\GeneratorTileService.cs" />
|
||||
<Compile Include="Tiles\MyVaultTileService.cs" />
|
||||
<Compile Include="Utilities\AndroidHelpers.cs" />
|
||||
<Compile Include="Utilities\CustomFingerprintDialogFragment.cs" />
|
||||
<Compile Include="Utilities\HockeyAppCrashManagerListener.cs" />
|
||||
<Compile Include="Utilities\StaticStore.cs" />
|
||||
<Compile Include="Utilities\AppCenterHelper.cs" />
|
||||
<Compile Include="WebAuthCallbackActivity.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidAsset Include="Assets\FontAwesome.ttf" />
|
||||
@@ -154,11 +156,48 @@
|
||||
<None Include="ci-build-apks.ps1" />
|
||||
<GoogleServicesJson Include="google-services.json" />
|
||||
<GoogleServicesJson Include="google-services.json.enc" />
|
||||
<None Include="fdroid-keystore.jks.enc" />
|
||||
<None Include="Properties\AndroidManifest.xml" />
|
||||
<None Include="upload-keystore.jks.enc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\logo_legacy.png" />
|
||||
<AndroidResource Include="Resources\drawable-hdpi\logo_white_legacy.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\logo_legacy.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\logo_white_legacy.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" />
|
||||
<AndroidResource Include="Resources\drawable\card.xml" />
|
||||
<AndroidResource Include="Resources\drawable\cog.xml" />
|
||||
<AndroidResource Include="Resources\drawable\icon.xml" />
|
||||
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
|
||||
<AndroidResource Include="Resources\drawable\id.xml" />
|
||||
<AndroidResource Include="Resources\drawable\lock.xml" />
|
||||
<AndroidResource Include="Resources\drawable\login.xml" />
|
||||
<AndroidResource Include="Resources\drawable\logo.xml" />
|
||||
<AndroidResource Include="Resources\drawable\logo_white.xml" />
|
||||
<AndroidResource Include="Resources\drawable\pencil.xml" />
|
||||
<AndroidResource Include="Resources\drawable\plus.xml" />
|
||||
<AndroidResource Include="Resources\drawable\refresh.xml" />
|
||||
<AndroidResource Include="Resources\drawable\search.xml" />
|
||||
<AndroidResource Include="Resources\drawable\shield.xml" />
|
||||
<AndroidResource Include="Resources\drawable-v23\splash_screen.xml" />
|
||||
<AndroidResource Include="Resources\drawable-v23\splash_screen_dark.xml" />
|
||||
<AndroidResource Include="Resources\layout\Tabbar.axml" />
|
||||
<AndroidResource Include="Resources\layout\Toolbar.axml" />
|
||||
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher.xml" />
|
||||
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher_round.xml" />
|
||||
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher.png" />
|
||||
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher_round.png" />
|
||||
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher.png" />
|
||||
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher_round.png" />
|
||||
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher.png" />
|
||||
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher_round.png" />
|
||||
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher.png" />
|
||||
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher_round.png" />
|
||||
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher.png" />
|
||||
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher_round.png" />
|
||||
<AndroidResource Include="Resources\values-night\styles.xml" />
|
||||
<AndroidResource Include="Resources\values\styles.xml" />
|
||||
<AndroidResource Include="Resources\values\colors.xml" />
|
||||
</ItemGroup>
|
||||
@@ -193,279 +232,18 @@
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\values\ic_launcher_background.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher_foreground.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher_round.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher_round.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher_foreground.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher_round.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher_foreground.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher_round.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher_foreground.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher_round.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher_foreground.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher_round.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\layout\autofill_listitem.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\login.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\login.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\login.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\login.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\login.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\logo.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\values-v21\styles.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\refresh.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\lock.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\logo.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\shield.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\autofill_use.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\accessibility_notification.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\accessibility_notification_icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\accessibility_step1.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\accessibility_step2.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\autofill_enable.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\autofill_use.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\accessibility_notification.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\accessibility_notification_icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step1.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step2.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\autofill_enable.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_notification.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_notification_icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step1.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step2.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\autofill_enable.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\autofill_use.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\autofill_use.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\logo.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_notification.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_notification_icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step1.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step2.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\autofill_enable.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\lock.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\refresh.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\refresh.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\lock.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\lock.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\refresh.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\lock.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\refresh.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\id.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\card.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\id.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\card.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\id.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\card.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\id.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\card.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\card.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\id.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\pencil.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\plus.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\pencil.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\plus.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\pencil.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\plus.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\pencil.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\plus.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\plus.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\pencil.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\slider_thumb.xml" />
|
||||
</ItemGroup>
|
||||
@@ -475,83 +253,23 @@
|
||||
<SubType>Designer</SubType>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\logo_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\logo_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\values\dimens.xml">
|
||||
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\shield.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\shield.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\shield.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\shield.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\refresh_sm.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\refresh_sm.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\refresh_sm.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\refresh_sm.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\refresh_sm.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\layout\CipherViewCell.axml">
|
||||
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\xml\app_restrictions.xml">
|
||||
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -1,14 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Android.Content;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Widget;
|
||||
using System.Linq;
|
||||
using Android.App;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App.Slices;
|
||||
using Android.Graphics;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Widget.Inline;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Enums;
|
||||
using Android.Views.Autofill;
|
||||
using AndroidX.AutoFill.Inline;
|
||||
using AndroidX.AutoFill.Inline.V1;
|
||||
using Bit.Core.Abstractions;
|
||||
using SaveFlags = Android.Service.Autofill.SaveFlags;
|
||||
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
@@ -16,51 +26,90 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
private static int _pendingIntentId = 0;
|
||||
|
||||
// These browser work natively with the autofill framework
|
||||
// These browsers work natively with the Autofill Framework
|
||||
//
|
||||
// Be sure:
|
||||
// - to keep these entries sorted alphabetically and
|
||||
//
|
||||
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
|
||||
public static HashSet<string> TrustedBrowsers = new HashSet<string>
|
||||
{
|
||||
"com.duckduckgo.mobile.android",
|
||||
"org.mozilla.focus",
|
||||
"org.mozilla.klar",
|
||||
"com.duckduckgo.mobile.android",
|
||||
};
|
||||
|
||||
// These browsers work using the compatibility shim for the autofill framework
|
||||
// These browsers work using the compatibility shim for the Autofill Framework
|
||||
//
|
||||
// Be sure:
|
||||
// - to keep these entries sorted alphabetically,
|
||||
// - to keep this list in sync with values in Resources/xml/autofillservice.xml, and
|
||||
//
|
||||
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
|
||||
public static HashSet<string> CompatBrowsers = new HashSet<string>
|
||||
{
|
||||
"org.mozilla.firefox",
|
||||
"org.mozilla.firefox_beta",
|
||||
"com.microsoft.emmx",
|
||||
"com.android.chrome",
|
||||
"com.chrome.beta",
|
||||
"com.amazon.cloud9",
|
||||
"com.android.browser",
|
||||
"com.android.chrome",
|
||||
"com.android.htmlviewer",
|
||||
"com.avast.android.secure.browser",
|
||||
"com.avg.android.secure.browser",
|
||||
"com.brave.browser",
|
||||
"com.brave.browser_beta",
|
||||
"com.brave.browser_default",
|
||||
"com.brave.browser_dev",
|
||||
"com.brave.browser_nightly",
|
||||
"com.chrome.beta",
|
||||
"com.chrome.canary",
|
||||
"com.chrome.dev",
|
||||
"com.ecosia.android",
|
||||
"com.google.android.apps.chrome",
|
||||
"com.google.android.apps.chrome_dev",
|
||||
"com.kiwibrowser.browser",
|
||||
"com.microsoft.emmx",
|
||||
"com.mmbox.browser",
|
||||
"com.mmbox.xbrowser",
|
||||
"com.naver.whale",
|
||||
"com.opera.browser",
|
||||
"com.opera.browser.beta",
|
||||
"com.opera.mini.native",
|
||||
"com.chrome.dev",
|
||||
"com.chrome.canary",
|
||||
"com.google.android.apps.chrome",
|
||||
"com.google.android.apps.chrome_dev",
|
||||
"com.yandex.browser",
|
||||
"com.opera.mini.native.beta",
|
||||
"com.opera.touch",
|
||||
"com.qwant.liberty",
|
||||
"com.sec.android.app.sbrowser",
|
||||
"com.sec.android.app.sbrowser.beta",
|
||||
"org.codeaurora.swe.browser",
|
||||
"com.amazon.cloud9",
|
||||
"com.stoutner.privacybrowser.free",
|
||||
"com.stoutner.privacybrowser.standard",
|
||||
"com.vivaldi.browser",
|
||||
"com.vivaldi.browser.snapshot",
|
||||
"com.vivaldi.browser.sopranos",
|
||||
"com.yandex.browser",
|
||||
"com.z28j.feel",
|
||||
"idm.internet.download.manager",
|
||||
"idm.internet.download.manager.adm.lite",
|
||||
"idm.internet.download.manager.plus",
|
||||
"io.github.forkmaintainers.iceraven",
|
||||
"mark.via",
|
||||
"mark.via.gp",
|
||||
"org.adblockplus.browser",
|
||||
"org.adblockplus.browser.beta",
|
||||
"org.bromite.bromite",
|
||||
"org.bromite.chromium",
|
||||
"org.chromium.chrome",
|
||||
"com.kiwibrowser.browser",
|
||||
"com.ecosia.android",
|
||||
"com.opera.mini.native.beta",
|
||||
"org.mozilla.fennec_aurora",
|
||||
"org.mozilla.fennec_fdroid",
|
||||
"com.qwant.liberty",
|
||||
"com.opera.touch",
|
||||
"org.codeaurora.swe.browser",
|
||||
"org.gnu.icecat",
|
||||
"org.mozilla.fenix",
|
||||
"org.mozilla.fenix.nightly",
|
||||
"org.mozilla.fennec_aurora",
|
||||
"org.mozilla.fennec_fdroid",
|
||||
"org.mozilla.firefox",
|
||||
"org.mozilla.firefox_beta",
|
||||
"org.mozilla.reference.browser",
|
||||
"org.mozilla.rocket",
|
||||
"com.vivaldi.browser",
|
||||
"org.torproject.torbrowser",
|
||||
"org.torproject.torbrowser_alpha",
|
||||
"org.ungoogled.chromium.extensions.stable",
|
||||
"org.ungoogled.chromium.stable",
|
||||
};
|
||||
|
||||
// The URLs are blacklisted from autofilling
|
||||
@@ -74,17 +123,17 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService cipherService)
|
||||
{
|
||||
if(parser.FieldCollection.FillableForLogin)
|
||||
if (parser.FieldCollection.FillableForLogin)
|
||||
{
|
||||
var ciphers = await cipherService.GetAllDecryptedByUrlAsync(parser.Uri);
|
||||
if(ciphers.Item1.Any() || ciphers.Item2.Any())
|
||||
if (ciphers.Item1.Any() || ciphers.Item2.Any())
|
||||
{
|
||||
var allCiphers = ciphers.Item1.ToList();
|
||||
allCiphers.AddRange(ciphers.Item2.ToList());
|
||||
return allCiphers.Select(c => new FilledItem(c)).ToList();
|
||||
}
|
||||
}
|
||||
else if(parser.FieldCollection.FillableForCard)
|
||||
else if (parser.FieldCollection.FillableForCard)
|
||||
{
|
||||
var ciphers = await cipherService.GetAllDecryptedAsync();
|
||||
return ciphers.Where(c => c.Type == CipherType.Card).Select(c => new FilledItem(c)).ToList();
|
||||
@@ -92,51 +141,105 @@ namespace Bit.Droid.Autofill
|
||||
return new List<FilledItem>();
|
||||
}
|
||||
|
||||
public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked)
|
||||
public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked,
|
||||
bool inlineAutofillEnabled, FillRequest fillRequest = null)
|
||||
{
|
||||
var responseBuilder = new FillResponse.Builder();
|
||||
if(items != null && items.Count > 0)
|
||||
// Acquire inline presentation specs on Android 11+
|
||||
IList<InlinePresentationSpec> inlinePresentationSpecs = null;
|
||||
var inlinePresentationSpecsCount = 0;
|
||||
var inlineMaxSuggestedCount = 0;
|
||||
if (inlineAutofillEnabled && fillRequest != null && (int)Build.VERSION.SdkInt >= 30)
|
||||
{
|
||||
foreach(var item in items)
|
||||
var inlineSuggestionsRequest = fillRequest.InlineSuggestionsRequest;
|
||||
inlineMaxSuggestedCount = inlineSuggestionsRequest?.MaxSuggestionCount ?? 0;
|
||||
inlinePresentationSpecs = inlineSuggestionsRequest?.InlinePresentationSpecs;
|
||||
inlinePresentationSpecsCount = inlinePresentationSpecs?.Count ?? 0;
|
||||
}
|
||||
|
||||
// Build response
|
||||
var responseBuilder = new FillResponse.Builder();
|
||||
if (items != null && items.Count > 0)
|
||||
{
|
||||
var maxItems = items.Count;
|
||||
if (inlineMaxSuggestedCount > 0)
|
||||
{
|
||||
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, item);
|
||||
if(dataset != null)
|
||||
// -1 to adjust for 'open vault' option
|
||||
maxItems = Math.Min(maxItems, inlineMaxSuggestedCount - 1);
|
||||
}
|
||||
for (int i = 0; i < maxItems; i++)
|
||||
{
|
||||
InlinePresentationSpec inlinePresentationSpec = null;
|
||||
if (inlinePresentationSpecs != null)
|
||||
{
|
||||
if (i < inlinePresentationSpecsCount)
|
||||
{
|
||||
inlinePresentationSpec = inlinePresentationSpecs[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the max suggestion count is larger than the number of specs in the list, then
|
||||
// the last spec is used for the remainder of the suggestions
|
||||
inlinePresentationSpec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1];
|
||||
}
|
||||
}
|
||||
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
|
||||
inlinePresentationSpec);
|
||||
if (dataset != null)
|
||||
{
|
||||
responseBuilder.AddDataset(dataset);
|
||||
}
|
||||
}
|
||||
}
|
||||
responseBuilder.AddDataset(BuildVaultDataset(parser.ApplicationContext, parser.FieldCollection,
|
||||
parser.Uri, locked));
|
||||
AddSaveInfo(parser, responseBuilder, parser.FieldCollection);
|
||||
parser.Uri, locked, inlinePresentationSpecs));
|
||||
AddSaveInfo(parser, fillRequest, responseBuilder, parser.FieldCollection);
|
||||
responseBuilder.SetIgnoredIds(parser.FieldCollection.IgnoreAutofillIds.ToArray());
|
||||
return responseBuilder.Build();
|
||||
}
|
||||
|
||||
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem)
|
||||
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
|
||||
InlinePresentationSpec inlinePresentationSpec = null)
|
||||
{
|
||||
var datasetBuilder = new Dataset.Builder(
|
||||
BuildListView(filledItem.Name, filledItem.Subtitle, filledItem.Icon, context));
|
||||
if(filledItem.ApplyToFields(fields, datasetBuilder))
|
||||
var overlayPresentation = BuildOverlayPresentation(
|
||||
filledItem.Name,
|
||||
filledItem.Subtitle,
|
||||
filledItem.Icon,
|
||||
context);
|
||||
|
||||
var inlinePresentation = BuildInlinePresentation(
|
||||
inlinePresentationSpec,
|
||||
filledItem.Name,
|
||||
filledItem.Subtitle,
|
||||
filledItem.Icon,
|
||||
null,
|
||||
context);
|
||||
|
||||
var datasetBuilder = new Dataset.Builder(overlayPresentation);
|
||||
if (inlinePresentation != null)
|
||||
{
|
||||
datasetBuilder.SetInlinePresentation(inlinePresentation);
|
||||
}
|
||||
if (filledItem.ApplyToFields(fields, datasetBuilder))
|
||||
{
|
||||
return datasetBuilder.Build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Dataset BuildVaultDataset(Context context, FieldCollection fields, string uri, bool locked)
|
||||
public static Dataset BuildVaultDataset(Context context, FieldCollection fields, string uri, bool locked,
|
||||
IList<InlinePresentationSpec> inlinePresentationSpecs = null)
|
||||
{
|
||||
var intent = new Intent(context, typeof(MainActivity));
|
||||
intent.PutExtra("autofillFramework", true);
|
||||
if(fields.FillableForLogin)
|
||||
if (fields.FillableForLogin)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
|
||||
}
|
||||
else if(fields.FillableForCard)
|
||||
else if (fields.FillableForCard)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
|
||||
}
|
||||
else if(fields.FillableForIdentity)
|
||||
else if (fields.FillableForIdentity)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
|
||||
}
|
||||
@@ -148,24 +251,36 @@ namespace Bit.Droid.Autofill
|
||||
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
||||
PendingIntentFlags.CancelCurrent);
|
||||
|
||||
var view = BuildListView(
|
||||
var overlayPresentation = BuildOverlayPresentation(
|
||||
AppResources.AutofillWithBitwarden,
|
||||
locked ? AppResources.VaultIsLocked : AppResources.GoToMyVault,
|
||||
Resource.Drawable.icon,
|
||||
context);
|
||||
|
||||
var datasetBuilder = new Dataset.Builder(view);
|
||||
datasetBuilder.SetAuthentication(pendingIntent.IntentSender);
|
||||
var inlinePresentation = BuildInlinePresentation(
|
||||
inlinePresentationSpecs?.Last(),
|
||||
AppResources.Bitwarden,
|
||||
locked ? AppResources.VaultIsLocked : AppResources.MyVault,
|
||||
Resource.Drawable.icon,
|
||||
pendingIntent,
|
||||
context);
|
||||
|
||||
var datasetBuilder = new Dataset.Builder(overlayPresentation);
|
||||
if (inlinePresentation != null)
|
||||
{
|
||||
datasetBuilder.SetInlinePresentation(inlinePresentation);
|
||||
}
|
||||
datasetBuilder.SetAuthentication(pendingIntent?.IntentSender);
|
||||
|
||||
// Dataset must have a value set. We will reset this in the main activity when the real item is chosen.
|
||||
foreach(var autofillId in fields.AutofillIds)
|
||||
foreach (var autofillId in fields.AutofillIds)
|
||||
{
|
||||
datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
|
||||
}
|
||||
return datasetBuilder.Build();
|
||||
}
|
||||
|
||||
public static RemoteViews BuildListView(string text, string subtext, int iconId, Context context)
|
||||
public static RemoteViews BuildOverlayPresentation(string text, string subtext, int iconId, Context context)
|
||||
{
|
||||
var packageName = context.PackageName;
|
||||
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
|
||||
@@ -175,29 +290,105 @@ namespace Bit.Droid.Autofill
|
||||
return view;
|
||||
}
|
||||
|
||||
public static void AddSaveInfo(Parser parser, FillResponse.Builder responseBuilder, FieldCollection fields)
|
||||
public static InlinePresentation BuildInlinePresentation(InlinePresentationSpec inlinePresentationSpec,
|
||||
string text, string subtext, int iconId, PendingIntent pendingIntent, Context context)
|
||||
{
|
||||
if ((int)Build.VERSION.SdkInt < 30 || inlinePresentationSpec == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (pendingIntent == null)
|
||||
{
|
||||
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
|
||||
// "my vault" presentation) so we're including an empty one here
|
||||
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
|
||||
PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent);
|
||||
}
|
||||
var slice = CreateInlinePresentationSlice(
|
||||
inlinePresentationSpec,
|
||||
text,
|
||||
subtext,
|
||||
iconId,
|
||||
"Autofill option",
|
||||
pendingIntent,
|
||||
context);
|
||||
if (slice != null)
|
||||
{
|
||||
return new InlinePresentation(slice, inlinePresentationSpec, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Slice CreateInlinePresentationSlice(
|
||||
InlinePresentationSpec inlinePresentationSpec,
|
||||
string text,
|
||||
string subtext,
|
||||
int iconId,
|
||||
string contentDescription,
|
||||
PendingIntent pendingIntent,
|
||||
Context context)
|
||||
{
|
||||
var imeStyle = inlinePresentationSpec.Style;
|
||||
if (!UiVersions.GetVersions(imeStyle).Contains(UiVersions.InlineUiVersion1))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var contentBuilder = InlineSuggestionUi.NewContentBuilder(pendingIntent)
|
||||
.SetContentDescription(contentDescription);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
contentBuilder.SetTitle(text);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(subtext))
|
||||
{
|
||||
contentBuilder.SetSubtitle(subtext);
|
||||
}
|
||||
if (iconId > 0)
|
||||
{
|
||||
var icon = Icon.CreateWithResource(context, iconId);
|
||||
if (icon != null)
|
||||
{
|
||||
if (iconId == Resource.Drawable.icon)
|
||||
{
|
||||
// Don't tint our logo
|
||||
icon.SetTintBlendMode(BlendMode.Dst);
|
||||
}
|
||||
contentBuilder.SetStartIcon(icon);
|
||||
}
|
||||
}
|
||||
return contentBuilder.Build().JavaCast<InlineSuggestionUi.Content>()?.Slice;
|
||||
}
|
||||
|
||||
public static void AddSaveInfo(Parser parser, FillRequest fillRequest, FillResponse.Builder responseBuilder,
|
||||
FieldCollection fields)
|
||||
{
|
||||
// Docs state that password fields cannot be reliably saved in Compat mode since they will show as
|
||||
// masked values.
|
||||
var compatBrowser = CompatBrowsers.Contains(parser.PackageName);
|
||||
if(compatBrowser && fields.SaveType == SaveDataType.Password)
|
||||
bool? compatRequest = null;
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q && fillRequest != null)
|
||||
{
|
||||
// Attempt to automatically establish compat request mode on Android 10+
|
||||
compatRequest = (fillRequest.Flags | FillRequest.FlagCompatibilityModeRequest) == fillRequest.Flags;
|
||||
}
|
||||
var compatBrowser = compatRequest ?? CompatBrowsers.Contains(parser.PackageName);
|
||||
if (compatBrowser && fields.SaveType == SaveDataType.Password)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var requiredIds = fields.GetRequiredSaveFields();
|
||||
if(fields.SaveType == SaveDataType.Generic || requiredIds.Length == 0)
|
||||
if (fields.SaveType == SaveDataType.Generic || requiredIds.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var saveBuilder = new SaveInfo.Builder(fields.SaveType, requiredIds);
|
||||
var optionalIds = fields.GetOptionalSaveIds();
|
||||
if(optionalIds.Length > 0)
|
||||
if (optionalIds.Length > 0)
|
||||
{
|
||||
saveBuilder.SetOptionalIds(optionalIds);
|
||||
}
|
||||
if(compatBrowser)
|
||||
if (compatBrowser)
|
||||
{
|
||||
saveBuilder.SetFlags(SaveFlags.SaveOnAllViewsInvisible);
|
||||
}
|
||||
|
||||
@@ -21,14 +21,16 @@ namespace Bit.Droid.Autofill
|
||||
public class AutofillService : Android.Service.Autofill.AutofillService
|
||||
{
|
||||
private ICipherService _cipherService;
|
||||
private ILockService _lockService;
|
||||
private IVaultTimeoutService _vaultTimeoutService;
|
||||
private IStorageService _storageService;
|
||||
private IPolicyService _policyService;
|
||||
private IUserService _userService;
|
||||
|
||||
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
|
||||
FillCallback callback)
|
||||
{
|
||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
||||
if(structure == null)
|
||||
if (structure == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -36,27 +38,29 @@ namespace Bit.Droid.Autofill
|
||||
var parser = new Parser(structure, ApplicationContext);
|
||||
parser.Parse();
|
||||
|
||||
if(_storageService == null)
|
||||
if (_storageService == null)
|
||||
{
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
}
|
||||
|
||||
var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
|
||||
if(!shouldAutofill)
|
||||
if (!shouldAutofill)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
|
||||
|
||||
if(_lockService == null)
|
||||
if (_vaultTimeoutService == null)
|
||||
{
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
}
|
||||
|
||||
List<FilledItem> items = null;
|
||||
var locked = await _lockService.IsLockedAsync();
|
||||
if(!locked)
|
||||
var locked = await _vaultTimeoutService.IsLockedAsync();
|
||||
if (!locked)
|
||||
{
|
||||
if(_cipherService == null)
|
||||
if (_cipherService == null)
|
||||
{
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
}
|
||||
@@ -64,34 +68,54 @@ namespace Bit.Droid.Autofill
|
||||
}
|
||||
|
||||
// build response
|
||||
var response = AutofillHelpers.BuildFillResponse(parser, items, locked);
|
||||
var response = AutofillHelpers.BuildFillResponse(parser, items, locked, inlineAutofillEnabled, request);
|
||||
callback.OnSuccess(response);
|
||||
}
|
||||
|
||||
public async override void OnSaveRequest(SaveRequest request, SaveCallback callback)
|
||||
{
|
||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
||||
if(structure == null)
|
||||
if (structure == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(_storageService == null)
|
||||
if (_storageService == null)
|
||||
{
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
}
|
||||
|
||||
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
|
||||
if(disableSavePrompt.GetValueOrDefault())
|
||||
if (disableSavePrompt.GetValueOrDefault())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_policyService ??= ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
|
||||
var personalOwnershipPolicies = await _policyService.GetAll(PolicyType.PersonalOwnership);
|
||||
if (personalOwnershipPolicies != null)
|
||||
{
|
||||
_userService ??= ServiceContainer.Resolve<IUserService>("userService");
|
||||
foreach (var policy in personalOwnershipPolicies)
|
||||
{
|
||||
if (policy.Enabled)
|
||||
{
|
||||
var org = await _userService.GetOrganizationAsync(policy.OrganizationId);
|
||||
if (org != null && org.Enabled && org.UsePolicies && !org.canManagePolicies
|
||||
&& org.Status == OrganizationUserStatusType.Confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parser = new Parser(structure, ApplicationContext);
|
||||
parser.Parse();
|
||||
|
||||
var savedItem = parser.FieldCollection.GetSavedItem();
|
||||
if(savedItem == null)
|
||||
if (savedItem == null)
|
||||
{
|
||||
Toast.MakeText(this, "Unable to save this form.", ToastLength.Short).Show();
|
||||
return;
|
||||
@@ -102,7 +126,7 @@ namespace Bit.Droid.Autofill
|
||||
intent.PutExtra("autofillFramework", true);
|
||||
intent.PutExtra("autofillFrameworkSave", true);
|
||||
intent.PutExtra("autofillFrameworkType", (int)savedItem.Type);
|
||||
switch(savedItem.Type)
|
||||
switch (savedItem.Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
intent.PutExtra("autofillFrameworkName", parser.Uri
|
||||
|
||||
@@ -31,26 +31,26 @@ namespace Bit.Droid.Autofill
|
||||
HtmlInfo = node.HtmlInfo;
|
||||
Node = node;
|
||||
|
||||
if(node.AutofillValue != null)
|
||||
if (node.AutofillValue != null)
|
||||
{
|
||||
if(node.AutofillValue.IsList)
|
||||
if (node.AutofillValue.IsList)
|
||||
{
|
||||
var autofillOptions = node.GetAutofillOptions();
|
||||
if(autofillOptions != null && autofillOptions.Length > 0)
|
||||
if (autofillOptions != null && autofillOptions.Length > 0)
|
||||
{
|
||||
ListValue = node.AutofillValue.ListValue;
|
||||
TextValue = autofillOptions[node.AutofillValue.ListValue];
|
||||
}
|
||||
}
|
||||
else if(node.AutofillValue.IsDate)
|
||||
else if (node.AutofillValue.IsDate)
|
||||
{
|
||||
DateValue = node.AutofillValue.DateValue;
|
||||
}
|
||||
else if(node.AutofillValue.IsText)
|
||||
else if (node.AutofillValue.IsText)
|
||||
{
|
||||
TextValue = node.AutofillValue.TextValue;
|
||||
}
|
||||
else if(node.AutofillValue.IsToggle)
|
||||
else if (node.AutofillValue.IsToggle)
|
||||
{
|
||||
ToggleValue = node.AutofillValue.ToggleValue;
|
||||
}
|
||||
@@ -93,20 +93,20 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if(this == obj)
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(obj == null || GetType() != obj.GetType())
|
||||
if (obj == null || GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var field = obj as Field;
|
||||
if(TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
|
||||
if (TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
|
||||
if (DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
private static bool IsValidHint(string hint)
|
||||
{
|
||||
switch(hint)
|
||||
switch (hint)
|
||||
{
|
||||
case View.AutofillHintCreditCardExpirationDate:
|
||||
case View.AutofillHintCreditCardExpirationDay:
|
||||
@@ -152,14 +152,14 @@ namespace Bit.Droid.Autofill
|
||||
private void UpdateSaveTypeFromHints()
|
||||
{
|
||||
SaveType = SaveDataType.Generic;
|
||||
if(_hints == null)
|
||||
if (_hints == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(var hint in _hints)
|
||||
foreach (var hint in _hints)
|
||||
{
|
||||
switch(hint)
|
||||
switch (hint)
|
||||
{
|
||||
case View.AutofillHintCreditCardExpirationDate:
|
||||
case View.AutofillHintCreditCardExpirationDay:
|
||||
|
||||
@@ -19,11 +19,11 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
get
|
||||
{
|
||||
if(FillableForLogin)
|
||||
if (FillableForLogin)
|
||||
{
|
||||
return SaveDataType.Password;
|
||||
}
|
||||
else if(FillableForCard)
|
||||
else if (FillableForCard)
|
||||
{
|
||||
return SaveDataType.CreditCard;
|
||||
}
|
||||
@@ -43,14 +43,14 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_passwordFields != null)
|
||||
if (_passwordFields != null)
|
||||
{
|
||||
return _passwordFields;
|
||||
}
|
||||
if(Hints.Any())
|
||||
if (Hints.Any())
|
||||
{
|
||||
_passwordFields = new List<Field>();
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
||||
{
|
||||
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
|
||||
}
|
||||
@@ -58,7 +58,7 @@ namespace Bit.Droid.Autofill
|
||||
else
|
||||
{
|
||||
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
||||
if(!_passwordFields.Any())
|
||||
if (!_passwordFields.Any())
|
||||
{
|
||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
||||
}
|
||||
@@ -71,29 +71,29 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_usernameFields != null)
|
||||
if (_usernameFields != null)
|
||||
{
|
||||
return _usernameFields;
|
||||
}
|
||||
_usernameFields = new List<Field>();
|
||||
if(Hints.Any())
|
||||
if (Hints.Any())
|
||||
{
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintEmailAddress))
|
||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintEmailAddress))
|
||||
{
|
||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintEmailAddress]);
|
||||
}
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintUsername))
|
||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintUsername))
|
||||
{
|
||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach(var passwordField in PasswordFields)
|
||||
foreach (var passwordField in PasswordFields)
|
||||
{
|
||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
||||
.LastOrDefault();
|
||||
if(usernameField != null)
|
||||
if (usernameField != null)
|
||||
{
|
||||
_usernameFields.Add(usernameField);
|
||||
}
|
||||
@@ -127,7 +127,7 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
public void Add(Field field)
|
||||
{
|
||||
if(field == null || FieldTrackingIds.Contains(field.TrackingId))
|
||||
if (field == null || FieldTrackingIds.Contains(field.TrackingId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -137,16 +137,16 @@ namespace Bit.Droid.Autofill
|
||||
Fields.Add(field);
|
||||
AutofillIds.Add(field.AutofillId);
|
||||
|
||||
if(field.Hints != null)
|
||||
if (field.Hints != null)
|
||||
{
|
||||
foreach(var hint in field.Hints)
|
||||
foreach (var hint in field.Hints)
|
||||
{
|
||||
Hints.Add(hint);
|
||||
if(field.Focused)
|
||||
if (field.Focused)
|
||||
{
|
||||
FocusedHints.Add(hint);
|
||||
}
|
||||
if(!HintToFieldsMap.ContainsKey(hint))
|
||||
if (!HintToFieldsMap.ContainsKey(hint))
|
||||
{
|
||||
HintToFieldsMap.Add(hint, new List<Field>());
|
||||
}
|
||||
@@ -157,10 +157,10 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
public SavedItem GetSavedItem()
|
||||
{
|
||||
if(SaveType == SaveDataType.Password)
|
||||
if (SaveType == SaveDataType.Password)
|
||||
{
|
||||
var passwordField = PasswordFields.FirstOrDefault(f => !string.IsNullOrWhiteSpace(f.TextValue));
|
||||
if(passwordField == null)
|
||||
if (passwordField == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ namespace Bit.Droid.Autofill
|
||||
savedItem.Login.Username = GetFieldValue(usernameField);
|
||||
return savedItem;
|
||||
}
|
||||
else if(SaveType == SaveDataType.CreditCard)
|
||||
else if (SaveType == SaveDataType.CreditCard)
|
||||
{
|
||||
var savedItem = new SavedItem
|
||||
{
|
||||
@@ -199,26 +199,26 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
public AutofillId[] GetOptionalSaveIds()
|
||||
{
|
||||
if(SaveType == SaveDataType.Password)
|
||||
if (SaveType == SaveDataType.Password)
|
||||
{
|
||||
return UsernameFields.Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
else if(SaveType == SaveDataType.CreditCard)
|
||||
else if (SaveType == SaveDataType.CreditCard)
|
||||
{
|
||||
var fieldList = new List<Field>();
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardSecurityCode))
|
||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardSecurityCode))
|
||||
{
|
||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardSecurityCode]);
|
||||
}
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationYear))
|
||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationYear))
|
||||
{
|
||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationYear]);
|
||||
}
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationMonth))
|
||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationMonth))
|
||||
{
|
||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationMonth]);
|
||||
}
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintName))
|
||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintName))
|
||||
{
|
||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintName]);
|
||||
}
|
||||
@@ -229,11 +229,11 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
public AutofillId[] GetRequiredSaveFields()
|
||||
{
|
||||
if(SaveType == SaveDataType.Password)
|
||||
if (SaveType == SaveDataType.Password)
|
||||
{
|
||||
return PasswordFields.Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
else if(SaveType == SaveDataType.CreditCard && HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber))
|
||||
else if (SaveType == SaveDataType.CreditCard && HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber))
|
||||
{
|
||||
return HintToFieldsMap[View.AutofillHintCreditCardNumber].Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
@@ -247,12 +247,12 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
private string GetFieldValue(string hint, bool monthValue = false)
|
||||
{
|
||||
if(HintToFieldsMap.ContainsKey(hint))
|
||||
if (HintToFieldsMap.ContainsKey(hint))
|
||||
{
|
||||
foreach(var field in HintToFieldsMap[hint])
|
||||
foreach (var field in HintToFieldsMap[hint])
|
||||
{
|
||||
var val = GetFieldValue(field, monthValue);
|
||||
if(!string.IsNullOrWhiteSpace(val))
|
||||
if (!string.IsNullOrWhiteSpace(val))
|
||||
{
|
||||
return val;
|
||||
}
|
||||
@@ -263,30 +263,30 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
private string GetFieldValue(Field field, bool monthValue = false)
|
||||
{
|
||||
if(field == null)
|
||||
if (field == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if(!string.IsNullOrWhiteSpace(field.TextValue))
|
||||
if (!string.IsNullOrWhiteSpace(field.TextValue))
|
||||
{
|
||||
if(field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
|
||||
if (field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
|
||||
{
|
||||
if(field.AutofillOptions.Count == 13)
|
||||
if (field.AutofillOptions.Count == 13)
|
||||
{
|
||||
return field.ListValue.ToString();
|
||||
}
|
||||
else if(field.AutofillOptions.Count == 12)
|
||||
else if (field.AutofillOptions.Count == 12)
|
||||
{
|
||||
return (field.ListValue + 1).ToString();
|
||||
}
|
||||
}
|
||||
return field.TextValue;
|
||||
}
|
||||
else if(field.DateValue.HasValue)
|
||||
else if (field.DateValue.HasValue)
|
||||
{
|
||||
return field.DateValue.Value.ToString();
|
||||
}
|
||||
else if(field.ToggleValue.HasValue)
|
||||
else if (field.ToggleValue.HasValue)
|
||||
{
|
||||
return field.ToggleValue.Value.ToString();
|
||||
}
|
||||
@@ -300,20 +300,20 @@ namespace Bit.Droid.Autofill
|
||||
f.InputType.HasFlag(InputTypes.TextVariationWebPassword);
|
||||
|
||||
// For whatever reason, multi-line input types are coming through with TextVariationPassword flags
|
||||
if(inputTypePassword && f.InputType.HasFlag(InputTypes.TextVariationPassword) &&
|
||||
if (inputTypePassword && f.InputType.HasFlag(InputTypes.TextVariationPassword) &&
|
||||
f.InputType.HasFlag(InputTypes.TextFlagMultiLine))
|
||||
{
|
||||
inputTypePassword = false;
|
||||
}
|
||||
|
||||
if(!inputTypePassword && f.HtmlInfo != null && f.HtmlInfo.Tag == "input" &&
|
||||
if (!inputTypePassword && f.HtmlInfo != null && f.HtmlInfo.Tag == "input" &&
|
||||
(f.HtmlInfo.Attributes?.Any() ?? false))
|
||||
{
|
||||
foreach(var a in f.HtmlInfo.Attributes)
|
||||
foreach (var a in f.HtmlInfo.Attributes)
|
||||
{
|
||||
var key = a.First as Java.Lang.String;
|
||||
var val = a.Second as Java.Lang.String;
|
||||
if(key != null && val != null && key.ToString() == "type" && val.ToString() == "password")
|
||||
if (key != null && val != null && key.ToString() == "type" && val.ToString() == "password")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -331,7 +331,7 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
private bool ValueContainsAnyTerms(string value, HashSet<string> terms)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(value))
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Bit.Droid.Autofill
|
||||
Type = cipher.Type;
|
||||
Subtitle = cipher.SubTitle;
|
||||
|
||||
switch(Type)
|
||||
switch (Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
Icon = Resource.Drawable.login;
|
||||
@@ -62,32 +62,32 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder)
|
||||
{
|
||||
if(!fieldCollection?.Fields.Any() ?? true)
|
||||
if (!fieldCollection?.Fields.Any() ?? true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var setValues = false;
|
||||
if(Type == CipherType.Login)
|
||||
if (Type == CipherType.Login)
|
||||
{
|
||||
if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password))
|
||||
if (fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password))
|
||||
{
|
||||
foreach(var f in fieldCollection.PasswordFields)
|
||||
foreach (var f in fieldCollection.PasswordFields)
|
||||
{
|
||||
var val = ApplyValue(f, _password);
|
||||
if(val != null)
|
||||
if (val != null)
|
||||
{
|
||||
setValues = true;
|
||||
datasetBuilder.SetValue(f.AutofillId, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
|
||||
if (fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
|
||||
{
|
||||
foreach(var f in fieldCollection.UsernameFields)
|
||||
foreach (var f in fieldCollection.UsernameFields)
|
||||
{
|
||||
var val = ApplyValue(f, Subtitle);
|
||||
if(val != null)
|
||||
if (val != null)
|
||||
{
|
||||
setValues = true;
|
||||
datasetBuilder.SetValue(f.AutofillId, val);
|
||||
@@ -95,59 +95,59 @@ namespace Bit.Droid.Autofill
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(Type == CipherType.Card)
|
||||
else if (Type == CipherType.Card)
|
||||
{
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardNumber,
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardNumber,
|
||||
_cardNumber))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardSecurityCode,
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardSecurityCode,
|
||||
_cardCode))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection,
|
||||
if (ApplyValue(datasetBuilder, fieldCollection,
|
||||
Android.Views.View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardExpirationYear,
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardExpirationYear,
|
||||
_cardExpYear))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, _cardName))
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, _cardName))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
}
|
||||
else if(Type == CipherType.Identity)
|
||||
else if (Type == CipherType.Identity)
|
||||
{
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPhone, _idPhone))
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPhone, _idPhone))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintEmailAddress, _idEmail))
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintEmailAddress, _idEmail))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintUsername,
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintUsername,
|
||||
_idUsername))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalAddress,
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalAddress,
|
||||
_idAddress))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalCode,
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalCode,
|
||||
_idPostalCode))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, Subtitle))
|
||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, Subtitle))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
@@ -159,12 +159,12 @@ namespace Bit.Droid.Autofill
|
||||
string hint, string value, bool monthValue = false)
|
||||
{
|
||||
bool setValues = false;
|
||||
if(fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value))
|
||||
if (fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
foreach(var f in fieldCollection.HintToFieldsMap[hint])
|
||||
foreach (var f in fieldCollection.HintToFieldsMap[hint])
|
||||
{
|
||||
var val = ApplyValue(f, value, monthValue);
|
||||
if(val != null)
|
||||
if (val != null)
|
||||
{
|
||||
setValues = true;
|
||||
builder.SetValue(f.AutofillId, val);
|
||||
@@ -176,31 +176,31 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
private static AutofillValue ApplyValue(Field field, string value, bool monthValue = false)
|
||||
{
|
||||
switch(field.AutofillType)
|
||||
switch (field.AutofillType)
|
||||
{
|
||||
case AutofillType.Date:
|
||||
if(long.TryParse(value, out long dateValue))
|
||||
if (long.TryParse(value, out long dateValue))
|
||||
{
|
||||
return AutofillValue.ForDate(dateValue);
|
||||
}
|
||||
break;
|
||||
case AutofillType.List:
|
||||
if(field.AutofillOptions != null)
|
||||
if (field.AutofillOptions != null)
|
||||
{
|
||||
if(monthValue && int.TryParse(value, out int monthIndex))
|
||||
if (monthValue && int.TryParse(value, out int monthIndex))
|
||||
{
|
||||
if(field.AutofillOptions.Count == 13)
|
||||
if (field.AutofillOptions.Count == 13)
|
||||
{
|
||||
return AutofillValue.ForList(monthIndex);
|
||||
}
|
||||
else if(field.AutofillOptions.Count >= monthIndex)
|
||||
else if (field.AutofillOptions.Count >= monthIndex)
|
||||
{
|
||||
return AutofillValue.ForList(monthIndex - 1);
|
||||
}
|
||||
}
|
||||
for(var i = 0; i < field.AutofillOptions.Count; i++)
|
||||
for (var i = 0; i < field.AutofillOptions.Count; i++)
|
||||
{
|
||||
if(field.AutofillOptions[i].Equals(value))
|
||||
if (field.AutofillOptions[i].Equals(value))
|
||||
{
|
||||
return AutofillValue.ForList(i);
|
||||
}
|
||||
@@ -210,7 +210,7 @@ namespace Bit.Droid.Autofill
|
||||
case AutofillType.Text:
|
||||
return AutofillValue.ForText(value);
|
||||
case AutofillType.Toggle:
|
||||
if(bool.TryParse(value, out bool toggleValue))
|
||||
if (bool.TryParse(value, out bool toggleValue))
|
||||
{
|
||||
return AutofillValue.ForToggle(toggleValue);
|
||||
}
|
||||
@@ -221,4 +221,4 @@ namespace Bit.Droid.Autofill
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,16 +33,16 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(_uri))
|
||||
if (!string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
var websiteNull = string.IsNullOrWhiteSpace(Website);
|
||||
if(websiteNull && string.IsNullOrWhiteSpace(PackageName))
|
||||
if (websiteNull && string.IsNullOrWhiteSpace(PackageName))
|
||||
{
|
||||
_uri = null;
|
||||
}
|
||||
else if(!websiteNull)
|
||||
else if (!websiteNull)
|
||||
{
|
||||
_uri = Website;
|
||||
}
|
||||
@@ -59,7 +59,7 @@ namespace Bit.Droid.Autofill
|
||||
get => _packageName;
|
||||
set
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(value))
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
_packageName = _uri = null;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ namespace Bit.Droid.Autofill
|
||||
get => _website;
|
||||
set
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(value))
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
_website = _uri = null;
|
||||
}
|
||||
@@ -84,10 +84,10 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
|
||||
FieldCollection != null && FieldCollection.Fillable;
|
||||
if(fillable)
|
||||
if (fillable)
|
||||
{
|
||||
var blacklistedUris = await storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
|
||||
if(blacklistedUris != null && blacklistedUris.Count > 0)
|
||||
if (blacklistedUris != null && blacklistedUris.Count > 0)
|
||||
{
|
||||
fillable = !new HashSet<string>(blacklistedUris).Contains(Uri);
|
||||
}
|
||||
@@ -98,20 +98,20 @@ namespace Bit.Droid.Autofill
|
||||
public void Parse()
|
||||
{
|
||||
string titlePackageId = null;
|
||||
for(var i = 0; i < _structure.WindowNodeCount; i++)
|
||||
for (var i = 0; i < _structure.WindowNodeCount; i++)
|
||||
{
|
||||
var node = _structure.GetWindowNodeAt(i);
|
||||
if(i == 0)
|
||||
if (i == 0)
|
||||
{
|
||||
titlePackageId = GetTitlePackageId(node);
|
||||
}
|
||||
ParseNode(node.RootViewNode);
|
||||
}
|
||||
if(string.IsNullOrWhiteSpace(PackageName) && string.IsNullOrWhiteSpace(Website))
|
||||
if (string.IsNullOrWhiteSpace(PackageName) && string.IsNullOrWhiteSpace(Website))
|
||||
{
|
||||
PackageName = titlePackageId;
|
||||
}
|
||||
if(!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
|
||||
if (!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
|
||||
!AutofillHelpers.CompatBrowsers.Contains(PackageName))
|
||||
{
|
||||
Website = null;
|
||||
@@ -123,7 +123,7 @@ namespace Bit.Droid.Autofill
|
||||
SetPackageAndDomain(node);
|
||||
var hints = node.GetAutofillHints();
|
||||
var isEditText = node.ClassName == "android.widget.EditText" || node?.HtmlInfo?.Tag == "input";
|
||||
if(isEditText || (hints?.Length ?? 0) > 0)
|
||||
if (isEditText || (hints?.Length ?? 0) > 0)
|
||||
{
|
||||
FieldCollection.Add(new Field(node));
|
||||
}
|
||||
@@ -132,7 +132,7 @@ namespace Bit.Droid.Autofill
|
||||
FieldCollection.IgnoreAutofillIds.Add(node.AutofillId);
|
||||
}
|
||||
|
||||
for(var i = 0; i < node.ChildCount; i++)
|
||||
for (var i = 0; i < node.ChildCount; i++)
|
||||
{
|
||||
ParseNode(node.GetChildAt(i));
|
||||
}
|
||||
@@ -140,15 +140,15 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
private void SetPackageAndDomain(ViewNode node)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) &&
|
||||
if (string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) &&
|
||||
!_excludedPackageIds.Contains(node.IdPackage))
|
||||
{
|
||||
PackageName = node.IdPackage;
|
||||
}
|
||||
if(string.IsNullOrWhiteSpace(Website) && !string.IsNullOrWhiteSpace(node.WebDomain))
|
||||
if (string.IsNullOrWhiteSpace(Website) && !string.IsNullOrWhiteSpace(node.WebDomain))
|
||||
{
|
||||
var scheme = "http";
|
||||
if((int)Build.VERSION.SdkInt >= 28)
|
||||
if ((int)Build.VERSION.SdkInt >= 28)
|
||||
{
|
||||
scheme = node.WebScheme;
|
||||
}
|
||||
@@ -158,13 +158,13 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
private string GetTitlePackageId(WindowNode node)
|
||||
{
|
||||
if(node != null && !string.IsNullOrWhiteSpace(node.Title))
|
||||
if (node != null && !string.IsNullOrWhiteSpace(node.Title))
|
||||
{
|
||||
var slashPosition = node.Title.IndexOf('/');
|
||||
if(slashPosition > -1)
|
||||
if (slashPosition > -1)
|
||||
{
|
||||
var packageId = node.Title.Substring(0, slashPosition);
|
||||
if(packageId.Contains("."))
|
||||
if (packageId.Contains("."))
|
||||
{
|
||||
return packageId;
|
||||
}
|
||||
|
||||
30
src/Android/Effects/FabShadowEffect.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Android.Graphics.Drawables;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportEffect(typeof(FabShadowEffect), "FabShadowEffect")]
|
||||
namespace Bit.Droid.Effects
|
||||
{
|
||||
public class FabShadowEffect : PlatformEffect
|
||||
{
|
||||
protected override void OnAttached ()
|
||||
{
|
||||
if (Control is Android.Widget.Button button)
|
||||
{
|
||||
var gd = new GradientDrawable();
|
||||
gd.SetColor(ThemeManager.GetResourceColor("FabColor").ToAndroid());
|
||||
gd.SetCornerRadius(100);
|
||||
|
||||
button.SetBackground(gd);
|
||||
button.Elevation = 6;
|
||||
button.TranslationZ = 20;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetached ()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
using Android.Support.Design.BottomNavigation;
|
||||
using Android.Support.Design.Widget;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Android.Widget;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
@@ -13,7 +10,7 @@ namespace Bit.Droid.Effects
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
if(Element is Label label && Control is TextView textView)
|
||||
if (Element is Label label && Control is TextView textView)
|
||||
{
|
||||
textView.SetTextSize(Android.Util.ComplexUnitType.Pt, (float)label.FontSize);
|
||||
}
|
||||
@@ -23,4 +20,4 @@ namespace Bit.Droid.Effects
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Android.Support.Design.BottomNavigation;
|
||||
using Android.Support.Design.Widget;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Android.Widget;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
@@ -13,7 +10,7 @@ namespace Bit.Droid.Effects
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
if(Control is TextView textView)
|
||||
if (Control is TextView textView)
|
||||
{
|
||||
textView.SetTextIsSelectable(true);
|
||||
}
|
||||
@@ -23,4 +20,4 @@ namespace Bit.Droid.Effects
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Android.Support.Design.BottomNavigation;
|
||||
using Android.Support.Design.Widget;
|
||||
using Android.Views;
|
||||
using Android.Views;
|
||||
using Bit.Droid.Effects;
|
||||
using Google.Android.Material.BottomNavigation;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
@@ -13,11 +12,11 @@ namespace Bit.Droid.Effects
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
if(!(Container.GetChildAt(0) is ViewGroup layout))
|
||||
if (!(Container.GetChildAt(0) is ViewGroup layout))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
|
||||
if (!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -28,4 +27,4 @@ namespace Bit.Droid.Effects
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,15 @@ using Bit.Core.Enums;
|
||||
using Android.Nfc;
|
||||
using Bit.App.Utilities;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Support.V4.Content;
|
||||
using AndroidX.Core.Content;
|
||||
using ZXing.Net.Mobile.Android;
|
||||
|
||||
namespace Bit.Droid
|
||||
{
|
||||
[Activity(
|
||||
Label = "Bitwarden",
|
||||
Icon = "@mipmap/ic_launcher",
|
||||
Theme = "@style/LightTheme.Splash",
|
||||
Theme = "@style/LaunchTheme",
|
||||
MainLauncher = true,
|
||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
|
||||
[Register("com.x8bit.bitwarden.MainActivity")]
|
||||
@@ -37,7 +38,7 @@ namespace Bit.Droid
|
||||
private IAppIdService _appIdService;
|
||||
private IStorageService _storageService;
|
||||
private IEventService _eventService;
|
||||
private PendingIntent _lockAlarmPendingIntent;
|
||||
private PendingIntent _vaultTimeoutAlarmPendingIntent;
|
||||
private PendingIntent _clearClipboardPendingIntent;
|
||||
private PendingIntent _eventUploadPendingIntent;
|
||||
private AppOptions _appOptions;
|
||||
@@ -51,7 +52,7 @@ namespace Bit.Droid
|
||||
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
|
||||
PendingIntentFlags.UpdateCurrent);
|
||||
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
|
||||
_lockAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
|
||||
_vaultTimeoutAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
|
||||
PendingIntentFlags.UpdateCurrent);
|
||||
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
|
||||
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
|
||||
@@ -73,14 +74,14 @@ namespace Bit.Droid
|
||||
|
||||
UpdateTheme(ThemeManager.GetTheme(true));
|
||||
base.OnCreate(savedInstanceState);
|
||||
if(!CoreHelpers.InDebugMode())
|
||||
if (!CoreHelpers.InDebugMode())
|
||||
{
|
||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||
}
|
||||
|
||||
#if !FDROID
|
||||
var hockeyAppListener = new HockeyAppCrashManagerListener(_appIdService, _userService);
|
||||
var hockeyAppTask = hockeyAppListener.InitAsync(this);
|
||||
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
|
||||
var appCenterTask = appCenterHelper.InitAsync();
|
||||
#endif
|
||||
|
||||
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
||||
@@ -90,44 +91,44 @@ namespace Bit.Droid
|
||||
|
||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
||||
{
|
||||
if(message.Command == "scheduleLockTimer")
|
||||
if (message.Command == "scheduleVaultTimeoutTimer")
|
||||
{
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
var lockOptionMinutes = (int)message.Data;
|
||||
var lockOptionMs = lockOptionMinutes * 60000;
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + lockOptionMs + 10;
|
||||
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _lockAlarmPendingIntent);
|
||||
var vaultTimeoutMinutes = (int)message.Data;
|
||||
var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + vaultTimeoutMs + 10;
|
||||
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _vaultTimeoutAlarmPendingIntent);
|
||||
}
|
||||
else if(message.Command == "cancelLockTimer")
|
||||
else if (message.Command == "cancelVaultTimeoutTimer")
|
||||
{
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
alarmManager.Cancel(_lockAlarmPendingIntent);
|
||||
alarmManager.Cancel(_vaultTimeoutAlarmPendingIntent);
|
||||
}
|
||||
else if(message.Command == "startEventTimer")
|
||||
else if (message.Command == "startEventTimer")
|
||||
{
|
||||
StartEventAlarm();
|
||||
}
|
||||
else if(message.Command == "stopEventTimer")
|
||||
else if (message.Command == "stopEventTimer")
|
||||
{
|
||||
var task = StopEventAlarmAsync();
|
||||
}
|
||||
else if(message.Command == "finishMainActivity")
|
||||
else if (message.Command == "finishMainActivity")
|
||||
{
|
||||
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish());
|
||||
}
|
||||
else if(message.Command == "listenYubiKeyOTP")
|
||||
else if (message.Command == "listenYubiKeyOTP")
|
||||
{
|
||||
ListenYubiKey((bool)message.Data);
|
||||
}
|
||||
else if(message.Command == "updatedTheme")
|
||||
else if (message.Command == "updatedTheme")
|
||||
{
|
||||
RestartApp();
|
||||
}
|
||||
else if(message.Command == "exit")
|
||||
else if (message.Command == "exit")
|
||||
{
|
||||
ExitApp();
|
||||
}
|
||||
else if(message.Command == "copiedToClipboard")
|
||||
else if (message.Command == "copiedToClipboard")
|
||||
{
|
||||
var task = ClearClipboardAlarmAsync(message.Data as Tuple<string, int?, bool>);
|
||||
}
|
||||
@@ -143,7 +144,8 @@ namespace Bit.Droid
|
||||
protected override void OnResume()
|
||||
{
|
||||
base.OnResume();
|
||||
if(_deviceActionService.SupportsNfc())
|
||||
Xamarin.Essentials.Platform.OnResume();
|
||||
if (_deviceActionService.SupportsNfc())
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -151,23 +153,24 @@ namespace Bit.Droid
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
var setRestrictions = AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this);
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
{
|
||||
base.OnNewIntent(intent);
|
||||
if(intent.GetBooleanExtra("generatorTile", false))
|
||||
if (intent.GetBooleanExtra("generatorTile", false))
|
||||
{
|
||||
_messagingService.Send("popAllAndGoToTabGenerator");
|
||||
if(_appOptions != null)
|
||||
if (_appOptions != null)
|
||||
{
|
||||
_appOptions.GeneratorTile = true;
|
||||
}
|
||||
}
|
||||
if(intent.GetBooleanExtra("myVaultTile", false))
|
||||
if (intent.GetBooleanExtra("myVaultTile", false))
|
||||
{
|
||||
_messagingService.Send("popAllAndGoToTabMyVault");
|
||||
if(_appOptions != null)
|
||||
if (_appOptions != null)
|
||||
{
|
||||
_appOptions.MyVaultTile = true;
|
||||
}
|
||||
@@ -181,9 +184,9 @@ namespace Bit.Droid
|
||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
|
||||
[GeneratedEnum] Permission[] grantResults)
|
||||
{
|
||||
if(requestCode == Constants.SelectFilePermissionRequestCode)
|
||||
if (requestCode == Constants.SelectFilePermissionRequestCode)
|
||||
{
|
||||
if(grantResults.Any(r => r != Permission.Granted))
|
||||
if (grantResults.Any(r => r != Permission.Granted))
|
||||
{
|
||||
_messagingService.Send("selectFileCameraPermissionDenied");
|
||||
}
|
||||
@@ -192,19 +195,19 @@ namespace Bit.Droid
|
||||
else
|
||||
{
|
||||
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(
|
||||
requestCode, permissions, grantResults);
|
||||
PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||
{
|
||||
if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok)
|
||||
if (resultCode == Result.Ok &&
|
||||
(requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode))
|
||||
{
|
||||
Android.Net.Uri uri = null;
|
||||
string fileName = null;
|
||||
if(data != null && data.Data != null)
|
||||
if (data != null && data.Data != null)
|
||||
{
|
||||
uri = data.Data;
|
||||
fileName = AndroidHelpers.GetFileName(ApplicationContext, uri);
|
||||
@@ -217,21 +220,29 @@ namespace Bit.Droid
|
||||
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
||||
}
|
||||
|
||||
if(uri == null)
|
||||
if (uri == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestCode == Constants.SaveFileRequestCode)
|
||||
{
|
||||
_messagingService.Send("selectSaveFileResult",
|
||||
new Tuple<string, string>(uri.ToString(), fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using(var stream = ContentResolver.OpenInputStream(uri))
|
||||
using(var memoryStream = new MemoryStream())
|
||||
using (var stream = ContentResolver.OpenInputStream(uri))
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(memoryStream);
|
||||
_messagingService.Send("selectFileResult",
|
||||
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
||||
}
|
||||
}
|
||||
catch(Java.IO.FileNotFoundException)
|
||||
catch (Java.IO.FileNotFoundException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -246,12 +257,12 @@ namespace Bit.Droid
|
||||
|
||||
private void ListenYubiKey(bool listen)
|
||||
{
|
||||
if(!_deviceActionService.SupportsNfc())
|
||||
if (!_deviceActionService.SupportsNfc())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
||||
if(listen)
|
||||
if (listen)
|
||||
{
|
||||
var intent = new Intent(this, Class);
|
||||
intent.AddFlags(ActivityFlags.SingleTop);
|
||||
@@ -288,11 +299,11 @@ namespace Bit.Droid
|
||||
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false)
|
||||
};
|
||||
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
||||
if(fillType > 0)
|
||||
if (fillType > 0)
|
||||
{
|
||||
options.FillType = (CipherType)fillType;
|
||||
}
|
||||
if(Intent.GetBooleanExtra("autofillFrameworkSave", false))
|
||||
if (Intent.GetBooleanExtra("autofillFrameworkSave", false))
|
||||
{
|
||||
options.SaveType = (CipherType)Intent.GetIntExtra("autofillFrameworkType", 0);
|
||||
options.SaveName = Intent.GetStringExtra("autofillFrameworkName");
|
||||
@@ -309,12 +320,12 @@ namespace Bit.Droid
|
||||
|
||||
private void ParseYubiKey(string data)
|
||||
{
|
||||
if(data == null)
|
||||
if (data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var otpMatch = _otpPattern.Matcher(data);
|
||||
if(otpMatch.Matches())
|
||||
if (otpMatch.Matches())
|
||||
{
|
||||
var otp = otpMatch.Group(1);
|
||||
_messagingService.Send("gotYubiKeyOTP", otp);
|
||||
@@ -323,21 +334,32 @@ namespace Bit.Droid
|
||||
|
||||
private void UpdateTheme(string theme)
|
||||
{
|
||||
if(theme == "dark")
|
||||
if (theme == "light")
|
||||
{
|
||||
SetTheme(Resource.Style.LightTheme);
|
||||
}
|
||||
else if (theme == "dark")
|
||||
{
|
||||
SetTheme(Resource.Style.DarkTheme);
|
||||
}
|
||||
else if(theme == "black")
|
||||
else if (theme == "black")
|
||||
{
|
||||
SetTheme(Resource.Style.BlackTheme);
|
||||
}
|
||||
else if(theme == "nord")
|
||||
else if (theme == "nord")
|
||||
{
|
||||
SetTheme(Resource.Style.NordTheme);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTheme(Resource.Style.LightTheme);
|
||||
if (_deviceActionService.UsingDarkTheme())
|
||||
{
|
||||
SetTheme(Resource.Style.DarkTheme);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTheme(Resource.Style.LightTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,24 +381,23 @@ namespace Bit.Droid
|
||||
|
||||
private async Task ClearClipboardAlarmAsync(Tuple<string, int?, bool> data)
|
||||
{
|
||||
if(data.Item3)
|
||||
if (data.Item3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var clearMs = data.Item2;
|
||||
if(clearMs == null)
|
||||
if (clearMs == null)
|
||||
{
|
||||
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
|
||||
if(clearSeconds != null)
|
||||
if (clearSeconds != null)
|
||||
{
|
||||
clearMs = clearSeconds.Value * 1000;
|
||||
}
|
||||
}
|
||||
if(clearMs == null)
|
||||
if (clearMs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
StaticStore.LastClipboardValue = data.Item1;
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs.Value;
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent);
|
||||
|
||||
@@ -37,14 +37,15 @@ namespace Bit.Droid
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership transer)
|
||||
: base(handle, transer)
|
||||
{
|
||||
if(ServiceContainer.RegisteredServices.Count == 0)
|
||||
if (ServiceContainer.RegisteredServices.Count == 0)
|
||||
{
|
||||
RegisterLocalServices();
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
|
||||
Constants.AndroidAllClearCipherCacheKeys);
|
||||
}
|
||||
#if !FDROID
|
||||
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
{
|
||||
ProviderInstaller.InstallIfNeededAsync(ApplicationContext, this);
|
||||
}
|
||||
@@ -70,7 +71,6 @@ namespace Bit.Droid
|
||||
{
|
||||
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
|
||||
|
||||
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
|
||||
// Note: This might cause a race condition. Investigate more.
|
||||
Task.Run(() =>
|
||||
{
|
||||
@@ -83,12 +83,10 @@ namespace Bit.Droid
|
||||
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
||||
});
|
||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
||||
CrossFingerprint.SetDialogFragmentType<CustomFingerprintDialogFragment>();
|
||||
|
||||
var preferencesStorage = new PreferencesStorageService(null);
|
||||
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
||||
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
||||
liteDbStorage.InitAsync();
|
||||
var localizeService = new LocalizeService();
|
||||
var broadcasterService = new BroadcasterService();
|
||||
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
||||
@@ -100,6 +98,7 @@ namespace Bit.Droid
|
||||
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||
broadcasterService);
|
||||
var biometricService = new BiometricService();
|
||||
|
||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
||||
@@ -110,6 +109,7 @@ namespace Bit.Droid
|
||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||
|
||||
// Push
|
||||
#if FDROID
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="1"
|
||||
android:versionName="2.2.7"
|
||||
android:versionName="2.8.2"
|
||||
android:installLocation="internalOnly"
|
||||
package="com.x8bit.bitwarden">
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="29" />
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
@@ -15,6 +16,7 @@
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
@@ -22,14 +24,14 @@
|
||||
|
||||
<application
|
||||
android:label="Bitwarden"
|
||||
android:theme="@style/LightTheme.Splash"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:allowBackup="false"
|
||||
tools:replace="android:allowBackup"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.x8bit.bitwarden.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
@@ -53,5 +55,13 @@
|
||||
</receiver>
|
||||
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
||||
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
|
||||
|
||||
<!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) -->
|
||||
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true" />
|
||||
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true" />
|
||||
|
||||
<!-- Support for LG "Dual Window" mode (for Android < 7.0 users) -->
|
||||
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyTitle("BitwardenAndroid")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("8bit Solutions LLC")]
|
||||
[assembly: AssemblyCompany("Bitwarden Inc.")]
|
||||
[assembly: AssemblyProduct("Bitwarden")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
@@ -16,12 +16,12 @@ namespace Bit.Droid.Push
|
||||
{
|
||||
public async override void OnMessageReceived(RemoteMessage message)
|
||||
{
|
||||
if(message?.Data == null)
|
||||
if (message?.Data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
||||
if(data == null)
|
||||
if (data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace Bit.Droid.Push
|
||||
"pushNotificationListenerService");
|
||||
await listener.OnMessageAsync(obj, Device.Android);
|
||||
}
|
||||
catch(JsonReaderException ex)
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex.ToString());
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using Android.Content;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Utilities;
|
||||
|
||||
namespace Bit.Droid.Receivers
|
||||
{
|
||||
@@ -12,11 +8,7 @@ namespace Bit.Droid.Receivers
|
||||
public override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
||||
if(StaticStore.LastClipboardValue != null && StaticStore.LastClipboardValue == clipboardManager.Text)
|
||||
{
|
||||
clipboardManager.Text = string.Empty;
|
||||
}
|
||||
StaticStore.LastClipboardValue = null;
|
||||
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace Bit.Droid.Receivers
|
||||
{
|
||||
public async override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
await lockService.CheckLockAsync();
|
||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
await vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
src/Android/Receivers/RestrictionsChangedReceiver.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Utilities;
|
||||
|
||||
namespace Bit.Droid.Receivers
|
||||
{
|
||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.RestrictionsChangedReceiver", Exported = false)]
|
||||
[IntentFilter(new[] { Intent.ActionApplicationRestrictionsChanged })]
|
||||
public class RestrictionsChangedReceiver : BroadcastReceiver
|
||||
{
|
||||
public async override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
if (intent.Action == Intent.ActionApplicationRestrictionsChanged)
|
||||
{
|
||||
await AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Android.Views;
|
||||
using Android.Views.InputMethods;
|
||||
using Android.Widget;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Droid.Renderers;
|
||||
using FFImageLoading;
|
||||
using FFImageLoading.Views;
|
||||
@@ -26,40 +27,40 @@ namespace Bit.Droid.Renderers
|
||||
private static Android.Graphics.Color _textColor;
|
||||
private static Android.Graphics.Color _mutedColor;
|
||||
private static Android.Graphics.Color _disabledIconColor;
|
||||
private static bool _usingLightTheme;
|
||||
|
||||
private AndroidCipherCell _cell;
|
||||
|
||||
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView,
|
||||
ViewGroup parent, Context context)
|
||||
{
|
||||
if(_faTypeface == null)
|
||||
// TODO expand beyond light/dark detection once we support custom theme switching without app restart
|
||||
var themeChanged = _usingLightTheme != ThemeManager.UsingLightTheme;
|
||||
if (_faTypeface == null)
|
||||
{
|
||||
_faTypeface = Typeface.CreateFromAsset(context.Assets, "FontAwesome.ttf");
|
||||
}
|
||||
if(_miTypeface == null)
|
||||
if (_miTypeface == null)
|
||||
{
|
||||
_miTypeface = Typeface.CreateFromAsset(context.Assets, "MaterialIcons_Regular.ttf");
|
||||
}
|
||||
if(_textColor == default(Android.Graphics.Color))
|
||||
if (_textColor == default(Android.Graphics.Color) || themeChanged)
|
||||
{
|
||||
_textColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["TextColor"])
|
||||
.ToAndroid();
|
||||
_textColor = ThemeManager.GetResourceColor("TextColor").ToAndroid();
|
||||
}
|
||||
if(_mutedColor == default(Android.Graphics.Color))
|
||||
if (_mutedColor == default(Android.Graphics.Color) || themeChanged)
|
||||
{
|
||||
_mutedColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["MutedColor"])
|
||||
.ToAndroid();
|
||||
_mutedColor = ThemeManager.GetResourceColor("MutedColor").ToAndroid();
|
||||
}
|
||||
if(_disabledIconColor == default(Android.Graphics.Color))
|
||||
if (_disabledIconColor == default(Android.Graphics.Color) || themeChanged)
|
||||
{
|
||||
_disabledIconColor =
|
||||
((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["DisabledIconColor"])
|
||||
.ToAndroid();
|
||||
_disabledIconColor = ThemeManager.GetResourceColor("DisabledIconColor").ToAndroid();
|
||||
}
|
||||
_usingLightTheme = ThemeManager.UsingLightTheme;
|
||||
|
||||
var cipherCell = item as CipherViewCell;
|
||||
_cell = convertView as AndroidCipherCell;
|
||||
if(_cell == null)
|
||||
if (_cell == null)
|
||||
{
|
||||
_cell = new AndroidCipherCell(context, cipherCell, _faTypeface, _miTypeface);
|
||||
}
|
||||
@@ -77,11 +78,11 @@ namespace Bit.Droid.Renderers
|
||||
{
|
||||
var cipherCell = sender as CipherViewCell;
|
||||
_cell.CipherViewCell = cipherCell;
|
||||
if(e.PropertyName == CipherViewCell.CipherProperty.PropertyName)
|
||||
if (e.PropertyName == CipherViewCell.CipherProperty.PropertyName)
|
||||
{
|
||||
_cell.UpdateCell(cipherCell);
|
||||
}
|
||||
else if(e.PropertyName == CipherViewCell.WebsiteIconsEnabledProperty.PropertyName)
|
||||
else if (e.PropertyName == CipherViewCell.WebsiteIconsEnabledProperty.PropertyName)
|
||||
{
|
||||
_cell.UpdateIconImage(cipherCell);
|
||||
}
|
||||
@@ -145,7 +146,7 @@ namespace Bit.Droid.Renderers
|
||||
|
||||
var cipher = cipherCell.Cipher;
|
||||
Name.Text = cipher.Name;
|
||||
if(!string.IsNullOrWhiteSpace(cipher.SubTitle))
|
||||
if (!string.IsNullOrWhiteSpace(cipher.SubTitle))
|
||||
{
|
||||
SubTitle.Text = cipher.SubTitle;
|
||||
SubTitle.Visibility = ViewStates.Visible;
|
||||
@@ -160,7 +161,7 @@ namespace Bit.Droid.Renderers
|
||||
|
||||
public void UpdateIconImage(CipherViewCell cipherCell)
|
||||
{
|
||||
if(_currentTask != null && !_currentTask.IsCancelled && !_currentTask.IsCompleted)
|
||||
if (_currentTask != null && !_currentTask.IsCancelled && !_currentTask.IsCompleted)
|
||||
{
|
||||
_currentTask.Cancel();
|
||||
}
|
||||
@@ -168,7 +169,7 @@ namespace Bit.Droid.Renderers
|
||||
var cipher = cipherCell.Cipher;
|
||||
|
||||
var iconImage = cipherCell.GetIconImage(cipher);
|
||||
if(iconImage.Item2 != null)
|
||||
if (iconImage.Item2 != null)
|
||||
{
|
||||
IconImage.SetImageResource(Resource.Drawable.login);
|
||||
IconImage.Visibility = ViewStates.Visible;
|
||||
@@ -197,7 +198,7 @@ namespace Bit.Droid.Renderers
|
||||
|
||||
private void MoreButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if(CipherViewCell.ButtonCommand?.CanExecute(CipherViewCell.Cipher) ?? false)
|
||||
if (CipherViewCell.ButtonCommand?.CanExecute(CipherViewCell.Cipher) ?? false)
|
||||
{
|
||||
CipherViewCell.ButtonCommand.Execute(CipherViewCell.Cipher);
|
||||
}
|
||||
@@ -205,7 +206,7 @@ namespace Bit.Droid.Renderers
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if(disposing)
|
||||
if (disposing)
|
||||
{
|
||||
MoreButton.Click -= MoreButton_Click;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,20 @@ namespace Bit.Droid.Renderers
|
||||
public CustomEditorRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
// Workaround for issue described here:
|
||||
// https://github.com/xamarin/Xamarin.Forms/issues/8291#issuecomment-617456651
|
||||
protected override void OnAttachedToWindow()
|
||||
{
|
||||
base.OnAttachedToWindow();
|
||||
EditText.Enabled = false;
|
||||
EditText.Enabled = true;
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null)
|
||||
if (Control != null && e.NewElement != null)
|
||||
{
|
||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||
Control.PaddingBottom + 20);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using Android.Content;
|
||||
using System.ComponentModel;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Android.Text;
|
||||
using Android.Views.InputMethods;
|
||||
using Android.Widget;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
@@ -16,7 +20,7 @@ namespace Bit.Droid.Renderers
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null)
|
||||
if (Control != null && e.NewElement != null)
|
||||
{
|
||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||
Control.PaddingBottom + 20);
|
||||
@@ -24,5 +28,46 @@ namespace Bit.Droid.Renderers
|
||||
(ImeAction)ImeFlags.NoExtractUi;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for failure to disable text prediction on non-password fields
|
||||
// see https://github.com/xamarin/Xamarin.Forms/issues/10857
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
|
||||
// Check if changed property is "IsPassword", otherwise ignore
|
||||
if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
|
||||
{
|
||||
// Check if field type is text, otherwise ignore (numeric passwords, etc.)
|
||||
EditText.InputType = Element.Keyboard.ToInputType();
|
||||
bool isText = (EditText.InputType & InputTypes.ClassText) == InputTypes.ClassText,
|
||||
isNumber = (EditText.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber;
|
||||
if (isText || isNumber)
|
||||
{
|
||||
if (Element.IsPassword)
|
||||
{
|
||||
// Element is a password field, set inputType to TextVariationPassword which disables
|
||||
// predictive text by default
|
||||
EditText.InputType = EditText.InputType |
|
||||
(isText ? InputTypes.TextVariationPassword : InputTypes.NumberVariationPassword);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Element is not a password field, set inputType to TextVariationVisiblePassword to
|
||||
// disable predictive text while still displaying the content.
|
||||
EditText.InputType = EditText.InputType |
|
||||
(isText ? InputTypes.TextVariationVisiblePassword : InputTypes.NumberVariationNormal);
|
||||
}
|
||||
|
||||
// The workaround above forces a reset of the style properties, so we need to re-apply the font.
|
||||
// see https://xamarin.github.io/bugzilla-archives/33/33666/bug.html
|
||||
var typeface = Typeface.CreateFromAsset(Context.Assets, "RobotoMono_Regular.ttf");
|
||||
if (Control is TextView label)
|
||||
{
|
||||
label.Typeface = typeface;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Bit.Droid.Renderers
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null)
|
||||
if (Control != null && e.NewElement != null)
|
||||
{
|
||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||
Control.PaddingBottom + 20);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Bit.Droid.Renderers
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null)
|
||||
if (Control != null && e.NewElement != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
60
src/Android/Renderers/CustomTabbedRenderer.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Android.Content;
|
||||
using Android.Views;
|
||||
using Bit.Droid.Renderers;
|
||||
using Google.Android.Material.BottomNavigation;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Xamarin.Forms.Platform.Android.AppCompat;
|
||||
|
||||
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CustomTabbedRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener
|
||||
{
|
||||
private TabbedPage _page;
|
||||
|
||||
public CustomTabbedRenderer(Context context) : base(context) { }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
_page = e.NewElement;
|
||||
GetBottomNavigationView()?.SetOnNavigationItemReselectedListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_page = e.OldElement;
|
||||
}
|
||||
}
|
||||
|
||||
private BottomNavigationView GetBottomNavigationView()
|
||||
{
|
||||
for (var i = 0; i < ViewGroup.ChildCount; i++)
|
||||
{
|
||||
var childView = ViewGroup.GetChildAt(i);
|
||||
if (childView is ViewGroup viewGroup)
|
||||
{
|
||||
for (var j = 0; j < viewGroup.ChildCount; j++)
|
||||
{
|
||||
var childRelativeLayoutView = viewGroup.GetChildAt(j);
|
||||
if (childRelativeLayoutView is BottomNavigationView bottomNavigationView)
|
||||
{
|
||||
return bottomNavigationView;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async void BottomNavigationView.IOnNavigationItemReselectedListener.OnNavigationItemReselected(IMenuItem item)
|
||||
{
|
||||
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
|
||||
{
|
||||
await _page.CurrentPage.Navigation.PopToRootAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ namespace Bit.Droid.Renderers
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null && e.NewElement is ExtendedListView listView)
|
||||
if (Control != null && e.NewElement != null && e.NewElement is ExtendedListView listView)
|
||||
{
|
||||
// Pad for FAB
|
||||
Control.SetPadding(0, 0, 0, 170);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Android.Content;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.Support.V4.Content.Res;
|
||||
using AndroidX.Core.Content.Resources;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
@@ -18,12 +18,12 @@ namespace Bit.Droid.Renderers
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && Element is ExtendedSlider view)
|
||||
if (Control != null && Element is ExtendedSlider view)
|
||||
{
|
||||
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
|
||||
if(t is GradientDrawable thumb)
|
||||
if (t is GradientDrawable thumb)
|
||||
{
|
||||
if(view.ThumbColor == Color.Default)
|
||||
if (view.ThumbColor == Color.Default)
|
||||
{
|
||||
thumb.SetColor(Color.White.ToAndroid());
|
||||
}
|
||||
|
||||
@@ -28,20 +28,20 @@ namespace Bit.Droid.Renderers
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if(Control == null)
|
||||
if (Control == null)
|
||||
{
|
||||
var webView = new AWebkit.WebView(_context);
|
||||
webView.Settings.JavaScriptEnabled = true;
|
||||
webView.SetWebViewClient(new JSWebViewClient(string.Format("javascript: {0}", JSFunction)));
|
||||
SetNativeControl(webView);
|
||||
}
|
||||
if(e.OldElement != null)
|
||||
if (e.OldElement != null)
|
||||
{
|
||||
Control.RemoveJavascriptInterface("jsBridge");
|
||||
var hybridWebView = e.OldElement as HybridWebView;
|
||||
hybridWebView.Cleanup();
|
||||
}
|
||||
if(e.NewElement != null)
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
|
||||
Control.LoadUrl(Element.Uri);
|
||||
@@ -51,7 +51,7 @@ namespace Bit.Droid.Renderers
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if(e.PropertyName == HybridWebView.UriProperty.PropertyName)
|
||||
if (e.PropertyName == HybridWebView.UriProperty.PropertyName)
|
||||
{
|
||||
Control.LoadUrl(Element.Uri);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ namespace Bit.Droid.Renderers
|
||||
[Export("invokeAction")]
|
||||
public void InvokeAction(string data)
|
||||
{
|
||||
if(_hybridWebViewRenderer != null &&
|
||||
if (_hybridWebViewRenderer != null &&
|
||||
_hybridWebViewRenderer.TryGetTarget(out HybridWebViewRenderer hybridRenderer))
|
||||
{
|
||||
hybridRenderer.Element.InvokeAction(data);
|
||||
|
||||
15674
src/Android/Resources/Resource.designer.cs
generated
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 775 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 487 B |
|
Before Width: | Height: | Size: 581 B |
|
Before Width: | Height: | Size: 904 B |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 495 B |
|
Before Width: | Height: | Size: 130 B |
|
Before Width: | Height: | Size: 943 B |
|
Before Width: | Height: | Size: 825 B |
|
Before Width: | Height: | Size: 635 B |
|
Before Width: | Height: | Size: 440 B |
15
src/Android/Resources/drawable-v23/splash_screen.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
TODO: When API 23 becomes our new minimum, replace 'splash_screen.xml' in 'drawable' with this version, and delete
|
||||
all versions of 'logo_legacy.png' and 'logo_white_legacy.png'. The bitmaps only exist due to a bug in API <23 where
|
||||
vector drawables used in layer-lists (our splash screen) stretch to fill the full height of the parent.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="@color/lightgray"/>
|
||||
</item>
|
||||
<item
|
||||
android:drawable="@drawable/logo"
|
||||
android:tileMode="disabled"
|
||||
android:gravity="center"/>
|
||||
</layer-list>
|
||||
15
src/Android/Resources/drawable-v23/splash_screen_dark.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
TODO: When API 23 becomes our new minimum, replace 'splash_screen_dark.xml' in 'drawable' with this version, and delete
|
||||
all versions of 'logo_legacy.png' and 'logo_white_legacy.png'. The bitmaps only exist due to a bug in API <23 where
|
||||
vector drawables used in layer-lists (our splash screen) stretch to fill the full height of the parent.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="@color/darkgray"/>
|
||||
</item>
|
||||
<item
|
||||
android:drawable="@drawable/logo_white"
|
||||
android:tileMode="disabled"
|
||||
android:gravity="center"/>
|
||||
</layer-list>
|
||||
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 941 B |