Compare commits
211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
3b7454961d | ||
|
|
88009e1a63 | ||
|
|
0afca29b0c | ||
|
|
46a75a2944 | ||
|
|
c099f82752 | ||
|
|
1da94bd9c8 | ||
|
|
96ce8165e9 | ||
|
|
f9b617339d | ||
|
|
58084810f3 | ||
|
|
429e62e6b5 | ||
|
|
b0b7f2afdf | ||
|
|
55f160d125 | ||
|
|
f6352f5392 | ||
|
|
ac7e90c0aa | ||
|
|
88fccfd6cd | ||
|
|
5fdf8e6045 | ||
|
|
d9907cdbeb | ||
|
|
d308f1ca3b | ||
|
|
0cdc138ba3 | ||
|
|
59d5314164 | ||
|
|
9c08a37772 | ||
|
|
b13f5356fe | ||
|
|
5f0c9725ce | ||
|
|
f951fea555 | ||
|
|
4b989b01e9 | ||
|
|
aed3ec5474 | ||
|
|
b354986199 | ||
|
|
e1983a7d66 | ||
|
|
0400d79f43 | ||
|
|
c911484632 | ||
|
|
713e441d2e | ||
|
|
d4b577732b | ||
|
|
440a410d7f | ||
|
|
37a536b138 | ||
|
|
a0aca3e837 | ||
|
|
b58c29111a | ||
|
|
b0f86ea161 | ||
|
|
93b59a75a4 | ||
|
|
54fcabaea6 | ||
|
|
0e966c0304 | ||
|
|
a363712127 | ||
|
|
4d8c665917 | ||
|
|
53bdd92e72 | ||
|
|
e51aa39ede | ||
|
|
33c82129ff | ||
|
|
7c5b8c0e9f | ||
|
|
9dc01bca1c | ||
|
|
3c7920b84c | ||
|
|
b92f3abbaf | ||
|
|
b6747a63ed | ||
|
|
41a44548d2 | ||
|
|
a79d3a0d7c | ||
|
|
f3a17709e5 | ||
|
|
ced9d33d2e | ||
|
|
23b1373f80 | ||
|
|
a80eb1f533 | ||
|
|
f657edf195 | ||
|
|
d34279dca5 | ||
|
|
954aa1112a | ||
|
|
b35a3339cb |
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,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 2017
|
||||
- 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
|
||||
}
|
||||
|
||||
@@ -279,6 +279,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 +307,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 +336,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
|
||||
|
||||
8
package-lock.json
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "bitwarden-fdroid",
|
||||
"name": "bitwarden-mobile",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
@@ -196,9 +196,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
|
||||
@@ -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,60 +20,91 @@ 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"),
|
||||
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.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", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
||||
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("org.mozilla.focus", "display_url"),
|
||||
new Browser("org.mozilla.klar", "display_url"),
|
||||
new Browser("mark.via.gp", "aw"),
|
||||
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.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"),
|
||||
new Browser("org.mozilla.fennec_aurora", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
||||
new Browser("org.mozilla.fennec_fdroid", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
||||
new Browser("org.mozilla.firefox", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
||||
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.reference.browser", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("org.mozilla.rocket", "display_url"),
|
||||
new Browser("org.torproject.torbrowser", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
||||
new Browser("org.torproject.torbrowser_alpha", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
||||
|
||||
// [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("mobi.mgeek.TunnyBrowser", "title"),
|
||||
new Browser("org.iron.srware", "url_bar"),
|
||||
}.ToDictionary(n => n.PackageName);
|
||||
|
||||
// Known packages to skip
|
||||
@@ -85,61 +124,88 @@ namespace Bit.Droid.Accessibility
|
||||
"com.teslacoilsw.launcher.prime",
|
||||
"is.shortcut",
|
||||
"me.craftsapp.nlauncher",
|
||||
"com.ss.squarehome2"
|
||||
"com.ss.squarehome2",
|
||||
"com.treydev.pns"
|
||||
};
|
||||
|
||||
// Be sure to keep these entries sorted alphabetically
|
||||
public static Dictionary<string, KnownUsernameField> KnownUsernameFields => new List<KnownUsernameField>
|
||||
{
|
||||
new KnownUsernameField("accounts.google.com", "ServiceLogin", "Email"),
|
||||
new KnownUsernameField("amazon.com", "signin", "ap_email_login"),
|
||||
new KnownUsernameField("github.com", "", "user[login]-footer"),
|
||||
new KnownUsernameField("paypal.com", "signin", "email"),
|
||||
new KnownUsernameField("signin.aws.amazon.com", "signin", "resolving_input"),
|
||||
new KnownUsernameField("signin.ebay.com", "eBayISAPI.dll", "userid"),
|
||||
}.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)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Node: {0} = {1}", node.id, node.text);
|
||||
}
|
||||
}
|
||||
|
||||
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("http://" + uri, UriKind.Absolute, out var uri2))
|
||||
{
|
||||
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
||||
if(urlPart != null)
|
||||
{
|
||||
uri = urlPart.Trim();
|
||||
}
|
||||
return string.Concat("http://", uri);
|
||||
}
|
||||
}
|
||||
if (Uri.TryCreate(uri, UriKind.Absolute, out var uri3))
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
@@ -149,11 +215,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;
|
||||
@@ -166,12 +232,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);
|
||||
}
|
||||
@@ -179,7 +244,7 @@ namespace Bit.Droid.Accessibility
|
||||
|
||||
public static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
||||
{
|
||||
if(editTextNode == null || value == null)
|
||||
if (editTextNode == null || value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -192,31 +257,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.");
|
||||
}
|
||||
@@ -226,26 +295,345 @@ 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 uriKey = null;
|
||||
string uriLocalPath = null;
|
||||
if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
|
||||
{
|
||||
uriKey = uri.Authority;
|
||||
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];
|
||||
if (uriLocalPath.EndsWith(usernameField.UriPathEnd))
|
||||
{
|
||||
foreach (var editText in allEditTexts)
|
||||
{
|
||||
foreach (var usernameViewId in usernameField.UsernameViewId.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.icon);
|
||||
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)
|
||||
{
|
||||
return windows?.Any(w => w.Title?.ToLower().Contains("autofill") ?? false) ?? 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 (!AccessibilityHelpers.IsAutofillTileAdded)
|
||||
{
|
||||
// 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 and
|
||||
// have _not_ added the autofill tile
|
||||
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
|
||||
Toast.MakeText(this, AppResources.AccessibilityOverlayPermissionAlert, 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
src/Android/Accessibility/KnownUsernameField.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
public class KnownUsernameField
|
||||
{
|
||||
public KnownUsernameField(string uriAuthority, string uriPathEnd, string usernameViewId)
|
||||
{
|
||||
UriAuthority = uriAuthority;
|
||||
UriPathEnd = uriPathEnd;
|
||||
UsernameViewId = usernameViewId;
|
||||
}
|
||||
|
||||
public string UriAuthority { get; set; }
|
||||
public string UriPathEnd { get; set; }
|
||||
public string UsernameViewId { 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>v9.0</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
|
||||
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
@@ -30,10 +29,8 @@
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
@@ -46,10 +43,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 +58,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 +75,22 @@
|
||||
<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.Essentials">
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.5.3.1</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.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v4" Version="28.0.0.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="28.0.0.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0.1" />
|
||||
<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 +100,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,13 +108,14 @@
|
||||
<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="Migration\AndroidKeyStoreStorageService.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" />
|
||||
@@ -140,12 +136,11 @@
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidAsset Include="Assets\FontAwesome.ttf" />
|
||||
@@ -155,11 +150,51 @@
|
||||
<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\accessibility_overlay.png" />
|
||||
<AndroidResource Include="Resources\drawable-hdpi\accessibility_permission.png" />
|
||||
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step1.png" />
|
||||
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step2.png" />
|
||||
<AndroidResource Include="Resources\drawable-hdpi\autofill_enable.png" />
|
||||
<AndroidResource Include="Resources\drawable-hdpi\autofill_use.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_overlay.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_permission.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step1.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step2.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\autofill_enable.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\autofill_use.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_overlay.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_permission.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step1.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step2.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\autofill_enable.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\autofill_use.png" />
|
||||
<AndroidResource Include="Resources\drawable\accessibility_overlay.png" />
|
||||
<AndroidResource Include="Resources\drawable\accessibility_permission.png" />
|
||||
<AndroidResource Include="Resources\drawable\accessibility_step1.png" />
|
||||
<AndroidResource Include="Resources\drawable\accessibility_step2.png" />
|
||||
<AndroidResource Include="Resources\drawable\autofill_enable.png" />
|
||||
<AndroidResource Include="Resources\drawable\autofill_use.png" />
|
||||
<AndroidResource Include="Resources\drawable\icon.xml" />
|
||||
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.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\styles.xml" />
|
||||
<AndroidResource Include="Resources\values\colors.xml" />
|
||||
</ItemGroup>
|
||||
@@ -194,57 +229,6 @@
|
||||
<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>
|
||||
@@ -287,87 +271,15 @@
|
||||
<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>
|
||||
@@ -422,21 +334,6 @@
|
||||
<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>
|
||||
@@ -554,5 +451,11 @@
|
||||
<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>
|
||||
@@ -16,73 +16,102 @@ 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.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.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",
|
||||
"mark.via.gp",
|
||||
"org.adblockplus.browser",
|
||||
"org.adblockplus.browser.beta",
|
||||
"org.bromite.bromite",
|
||||
"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",
|
||||
"org.torproject.torbrowser",
|
||||
"org.torproject.torbrowser_alpha",
|
||||
};
|
||||
|
||||
// The URLs are blacklisted from autofilling
|
||||
public static HashSet<string> BlacklistedUris = new HashSet<string>
|
||||
{
|
||||
"androidapp://android",
|
||||
"androidapp://com.android.settings",
|
||||
"androidapp://com.x8bit.bitwarden",
|
||||
"androidapp://com.oneplus.applocker",
|
||||
};
|
||||
|
||||
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();
|
||||
@@ -93,12 +122,12 @@ namespace Bit.Droid.Autofill
|
||||
public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked)
|
||||
{
|
||||
var responseBuilder = new FillResponse.Builder();
|
||||
if(items != null && items.Count > 0)
|
||||
if (items != null && items.Count > 0)
|
||||
{
|
||||
foreach(var item in items)
|
||||
foreach (var item in items)
|
||||
{
|
||||
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, item);
|
||||
if(dataset != null)
|
||||
if (dataset != null)
|
||||
{
|
||||
responseBuilder.AddDataset(dataset);
|
||||
}
|
||||
@@ -115,7 +144,7 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
var datasetBuilder = new Dataset.Builder(
|
||||
BuildListView(filledItem.Name, filledItem.Subtitle, filledItem.Icon, context));
|
||||
if(filledItem.ApplyToFields(fields, datasetBuilder))
|
||||
if (filledItem.ApplyToFields(fields, datasetBuilder))
|
||||
{
|
||||
return datasetBuilder.Build();
|
||||
}
|
||||
@@ -126,15 +155,15 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -156,7 +185,7 @@ namespace Bit.Droid.Autofill
|
||||
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"));
|
||||
}
|
||||
@@ -178,24 +207,24 @@ namespace Bit.Droid.Autofill
|
||||
// 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)
|
||||
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,14 @@ namespace Bit.Droid.Autofill
|
||||
public class AutofillService : Android.Service.Autofill.AutofillService
|
||||
{
|
||||
private ICipherService _cipherService;
|
||||
private ILockService _lockService;
|
||||
private IVaultTimeoutService _vaultTimeoutService;
|
||||
private IStorageService _storageService;
|
||||
|
||||
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 +36,27 @@ 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;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
@@ -71,18 +71,18 @@ namespace Bit.Droid.Autofill
|
||||
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;
|
||||
}
|
||||
@@ -91,7 +91,7 @@ namespace Bit.Droid.Autofill
|
||||
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 +102,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);
|
||||
}
|
||||
|
||||
@@ -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,7 +17,7 @@ using Bit.Core.Enums;
|
||||
using Android.Nfc;
|
||||
using Bit.App.Utilities;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Support.V4.Content;
|
||||
using AndroidX.Core.Content;
|
||||
|
||||
namespace Bit.Droid
|
||||
{
|
||||
@@ -37,7 +37,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 +51,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 +73,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 +90,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 +143,7 @@ namespace Bit.Droid
|
||||
protected override void OnResume()
|
||||
{
|
||||
base.OnResume();
|
||||
if(_deviceActionService.SupportsNfc())
|
||||
if (_deviceActionService.SupportsNfc())
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -151,23 +151,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 +182,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");
|
||||
}
|
||||
@@ -200,11 +201,12 @@ namespace Bit.Droid
|
||||
|
||||
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 +219,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 +256,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 +298,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 +319,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,15 +333,15 @@ namespace Bit.Droid
|
||||
|
||||
private void UpdateTheme(string theme)
|
||||
{
|
||||
if(theme == "dark")
|
||||
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);
|
||||
}
|
||||
@@ -359,24 +369,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,19 +37,14 @@ 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);
|
||||
if(App.Migration.MigrationHelpers.NeedsMigration())
|
||||
{
|
||||
var task = App.Migration.MigrationHelpers.PerformMigrationAsync();
|
||||
Task.Delay(2000).Wait();
|
||||
}
|
||||
}
|
||||
#if !FDROID
|
||||
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
{
|
||||
ProviderInstaller.InstallIfNeededAsync(ApplicationContext, this);
|
||||
}
|
||||
@@ -74,14 +69,7 @@ namespace Bit.Droid
|
||||
private void RegisterLocalServices()
|
||||
{
|
||||
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
|
||||
ServiceContainer.Register("settingsShim", new App.Migration.SettingsShim());
|
||||
if(App.Migration.MigrationHelpers.NeedsMigration())
|
||||
{
|
||||
ServiceContainer.Register<App.Migration.Abstractions.IOldSecureStorageService>(
|
||||
"oldSecureStorageService", new Migration.AndroidKeyStoreStorageService());
|
||||
}
|
||||
|
||||
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
|
||||
// Note: This might cause a race condition. Investigate more.
|
||||
Task.Run(() =>
|
||||
{
|
||||
@@ -94,7 +82,6 @@ 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);
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
using Java.Security;
|
||||
using Javax.Crypto;
|
||||
using Android.OS;
|
||||
using Bit.App.Abstractions;
|
||||
using System;
|
||||
using Android.Security;
|
||||
using Javax.Security.Auth.X500;
|
||||
using Java.Math;
|
||||
using Android.Security.Keystore;
|
||||
using Android.App;
|
||||
using Java.Util;
|
||||
using Javax.Crypto.Spec;
|
||||
using Android.Preferences;
|
||||
using Bit.App.Migration;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.App.Migration.Abstractions;
|
||||
|
||||
namespace Bit.Droid.Migration
|
||||
{
|
||||
public class AndroidKeyStoreStorageService : IOldSecureStorageService
|
||||
{
|
||||
private const string AndroidKeyStore = "AndroidKeyStore";
|
||||
private const string AesMode = "AES/GCM/NoPadding";
|
||||
|
||||
private const string KeyAlias = "bitwardenKey2";
|
||||
private const string KeyAliasV1 = "bitwardenKey";
|
||||
|
||||
private const string SettingsFormat = "ksSecured2:{0}";
|
||||
private const string SettingsFormatV1 = "ksSecured:{0}";
|
||||
|
||||
private const string AesKey = "ksSecured2:aesKeyForService";
|
||||
private const string AesKeyV1 = "ksSecured:aesKeyForService";
|
||||
|
||||
private readonly string _rsaMode;
|
||||
private readonly bool _oldAndroid;
|
||||
private readonly SettingsShim _settings;
|
||||
private readonly KeyStore _keyStore;
|
||||
|
||||
public AndroidKeyStoreStorageService()
|
||||
{
|
||||
_oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
|
||||
_rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
|
||||
|
||||
_settings = ServiceContainer.Resolve<SettingsShim>("settingsShim");
|
||||
|
||||
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
|
||||
_keyStore.Load(null);
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
GenerateStoreKey(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
GenerateStoreKey(false);
|
||||
}
|
||||
|
||||
GenerateAesKey();
|
||||
*/
|
||||
}
|
||||
|
||||
public bool Contains(string key)
|
||||
{
|
||||
return _settings.Contains(string.Format(SettingsFormat, key)) ||
|
||||
_settings.Contains(string.Format(SettingsFormatV1, key));
|
||||
}
|
||||
|
||||
public void Delete(string key)
|
||||
{
|
||||
CleanupOld(key);
|
||||
|
||||
var formattedKey = string.Format(SettingsFormat, key);
|
||||
if(_settings.Contains(formattedKey))
|
||||
{
|
||||
_settings.Remove(formattedKey);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Retrieve(string key)
|
||||
{
|
||||
var formattedKey = string.Format(SettingsFormat, key);
|
||||
if(!_settings.Contains(formattedKey))
|
||||
{
|
||||
return TryGetAndMigrate(key);
|
||||
}
|
||||
|
||||
var cs = _settings.GetValueOrDefault(formattedKey, null);
|
||||
if(string.IsNullOrWhiteSpace(cs))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var aesKey = GetAesKey();
|
||||
if(aesKey == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Failed to decrypt from secure storage.");
|
||||
_settings.Remove(formattedKey);
|
||||
//Utilities.SendCrashEmail(e);
|
||||
//Utilities.SaveCrashFile(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Store(string key, byte[] dataBytes)
|
||||
{
|
||||
var formattedKey = string.Format(SettingsFormat, key);
|
||||
CleanupOld(key);
|
||||
if(dataBytes == null)
|
||||
{
|
||||
_settings.Remove(formattedKey);
|
||||
return;
|
||||
}
|
||||
|
||||
var aesKey = GetAesKey();
|
||||
if(aesKey == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var cipherString = App.Migration.Crypto.AesCbcEncrypt(dataBytes, aesKey);
|
||||
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Failed to encrypt to secure storage.");
|
||||
//Utilities.SendCrashEmail(e);
|
||||
//Utilities.SaveCrashFile(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateStoreKey(bool withDate)
|
||||
{
|
||||
if(_keyStore.ContainsAlias(KeyAlias))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearSettings();
|
||||
|
||||
var end = Calendar.Instance;
|
||||
end.Add(CalendarField.Year, 99);
|
||||
|
||||
if(_oldAndroid)
|
||||
{
|
||||
var subject = new X500Principal($"CN={KeyAlias}");
|
||||
|
||||
var builder = new KeyPairGeneratorSpec.Builder(Application.Context)
|
||||
.SetAlias(KeyAlias)
|
||||
.SetSubject(subject)
|
||||
.SetSerialNumber(BigInteger.Ten);
|
||||
|
||||
if(withDate)
|
||||
{
|
||||
builder.SetStartDate(new Date(0)).SetEndDate(end.Time);
|
||||
}
|
||||
|
||||
var spec = builder.Build();
|
||||
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
|
||||
gen.Initialize(spec);
|
||||
gen.GenerateKeyPair();
|
||||
}
|
||||
else
|
||||
{
|
||||
var builder = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
||||
.SetBlockModes(KeyProperties.BlockModeGcm)
|
||||
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone);
|
||||
|
||||
if(withDate)
|
||||
{
|
||||
builder.SetKeyValidityStart(new Date(0)).SetKeyValidityEnd(end.Time);
|
||||
}
|
||||
|
||||
var spec = builder.Build();
|
||||
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
|
||||
gen.Init(spec);
|
||||
gen.GenerateKey();
|
||||
}
|
||||
}
|
||||
|
||||
private KeyStore.PrivateKeyEntry GetRsaKeyEntry(string alias)
|
||||
{
|
||||
return _keyStore.GetEntry(alias, null) as KeyStore.PrivateKeyEntry;
|
||||
}
|
||||
|
||||
private void GenerateAesKey()
|
||||
{
|
||||
if(_settings.Contains(AesKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var key = App.Migration.Crypto.RandomBytes(512 / 8);
|
||||
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
|
||||
_settings.AddOrUpdateValue(AesKey, encKey);
|
||||
}
|
||||
|
||||
private App.Migration.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var aesKey = v1 ? AesKeyV1 : AesKey;
|
||||
if(!_settings.Contains(aesKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var encKey = _settings.GetValueOrDefault(aesKey, null);
|
||||
if(string.IsNullOrWhiteSpace(encKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(_oldAndroid || v1)
|
||||
{
|
||||
var encKeyBytes = Convert.FromBase64String(encKey);
|
||||
var key = RsaDecrypt(encKeyBytes, v1);
|
||||
return new App.Migration.Models.SymmetricCryptoKey(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parts = encKey.Split('|');
|
||||
if(parts.Length < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ivBytes = Convert.FromBase64String(parts[0]);
|
||||
var encKeyBytes = Convert.FromBase64String(parts[1]);
|
||||
var key = AesDecrypt(ivBytes, encKeyBytes);
|
||||
return new App.Migration.Models.SymmetricCryptoKey(key);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Cannot get AesKey.");
|
||||
_keyStore.DeleteEntry(KeyAlias);
|
||||
_settings.Remove(AesKey);
|
||||
if(!v1)
|
||||
{
|
||||
//Utilities.SendCrashEmail(e);
|
||||
//Utilities.SaveCrashFile(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string AesEncrypt(byte[] input)
|
||||
{
|
||||
using(var entry = _keyStore.GetKey(KeyAlias, null))
|
||||
using(var cipher = Cipher.GetInstance(AesMode))
|
||||
{
|
||||
cipher.Init(CipherMode.EncryptMode, entry);
|
||||
var encBytes = cipher.DoFinal(input);
|
||||
var ivBytes = cipher.GetIV();
|
||||
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] AesDecrypt(byte[] iv, byte[] encData)
|
||||
{
|
||||
using(var entry = _keyStore.GetKey(KeyAlias, null))
|
||||
using(var cipher = Cipher.GetInstance(AesMode))
|
||||
{
|
||||
var spec = new GCMParameterSpec(128, iv);
|
||||
cipher.Init(CipherMode.DecryptMode, entry, spec);
|
||||
var decBytes = cipher.DoFinal(encData);
|
||||
return decBytes;
|
||||
}
|
||||
}
|
||||
|
||||
private string RsaEncrypt(byte[] data)
|
||||
{
|
||||
using(var entry = GetRsaKeyEntry(KeyAlias))
|
||||
using(var cipher = Cipher.GetInstance(_rsaMode))
|
||||
{
|
||||
cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
|
||||
var cipherText = cipher.DoFinal(data);
|
||||
return Convert.ToBase64String(cipherText);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] RsaDecrypt(byte[] encData, bool v1)
|
||||
{
|
||||
using(var entry = GetRsaKeyEntry(v1 ? KeyAliasV1 : KeyAlias))
|
||||
using(var cipher = Cipher.GetInstance(_rsaMode))
|
||||
{
|
||||
if(_oldAndroid)
|
||||
{
|
||||
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey, OAEPParameterSpec.Default);
|
||||
}
|
||||
|
||||
var plainText = cipher.DoFinal(encData);
|
||||
return plainText;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] TryGetAndMigrate(string key)
|
||||
{
|
||||
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
|
||||
if(_settings.Contains(formattedKeyV1))
|
||||
{
|
||||
var aesKeyV1 = GetAesKey(true);
|
||||
if(aesKeyV1 != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cs = _settings.GetValueOrDefault(formattedKeyV1, null);
|
||||
var value = App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKeyV1);
|
||||
Store(key, value);
|
||||
return value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Failed to decrypt v1 from secure storage.");
|
||||
}
|
||||
}
|
||||
|
||||
_settings.Remove(formattedKeyV1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CleanupOld(string key)
|
||||
{
|
||||
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
|
||||
if(_settings.Contains(formattedKeyV1))
|
||||
{
|
||||
_settings.Remove(formattedKeyV1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearSettings(string format = SettingsFormat)
|
||||
{
|
||||
var prefix = string.Format(format, string.Empty);
|
||||
|
||||
using(var sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(Application.Context))
|
||||
using(var sharedPreferencesEditor = sharedPreferences.Edit())
|
||||
{
|
||||
var removed = false;
|
||||
foreach(var pref in sharedPreferences.All)
|
||||
{
|
||||
if(pref.Key.StartsWith(prefix))
|
||||
{
|
||||
removed = true;
|
||||
sharedPreferencesEditor.Remove(pref.Key);
|
||||
}
|
||||
}
|
||||
|
||||
if(removed)
|
||||
{
|
||||
sharedPreferencesEditor.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,19 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="1"
|
||||
android:versionName="2.2.2"
|
||||
android:versionName="2.4.2"
|
||||
package="com.x8bit.bitwarden">
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28" />
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<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" />
|
||||
@@ -28,7 +30,7 @@
|
||||
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">
|
||||
@@ -52,5 +54,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", string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -32,34 +33,30 @@ namespace Bit.Droid.Renderers
|
||||
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView,
|
||||
ViewGroup parent, Context context)
|
||||
{
|
||||
if(_faTypeface == null)
|
||||
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))
|
||||
{
|
||||
_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))
|
||||
{
|
||||
_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))
|
||||
{
|
||||
_disabledIconColor =
|
||||
((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["DisabledIconColor"])
|
||||
.ToAndroid();
|
||||
_disabledIconColor = ThemeManager.GetResourceColor("DisabledIconColor").ToAndroid();
|
||||
}
|
||||
|
||||
var cipherCell = item as CipherViewCell;
|
||||
_cell = convertView as AndroidCipherCell;
|
||||
if(_cell == null)
|
||||
if (_cell == null)
|
||||
{
|
||||
_cell = new AndroidCipherCell(context, cipherCell, _faTypeface, _miTypeface);
|
||||
}
|
||||
@@ -77,11 +74,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 +142,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 +157,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 +165,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 +194,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 +202,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);
|
||||
|
||||
@@ -16,7 +16,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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
15621
src/Android/Resources/Resource.designer.cs
generated
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
BIN
src/Android/Resources/drawable-hdpi/accessibility_overlay.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/Android/Resources/drawable-hdpi/accessibility_permission.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
BIN
src/Android/Resources/drawable-xhdpi/accessibility_overlay.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 11 KiB |
BIN
src/Android/Resources/drawable-xxhdpi/accessibility_overlay.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
BIN
src/Android/Resources/drawable/accessibility_overlay.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/Android/Resources/drawable/accessibility_permission.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 13 KiB |
13
src/Android/Resources/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="1024.4897"
|
||||
android:viewportHeight="1024.4897">
|
||||
<group android:translateX="300.2449"
|
||||
android:translateY="261.2449">
|
||||
<path
|
||||
android:pathData="M393.4,52.2C389.7,48.5 385.3,46.6 380.3,46.6L43.7,46.6C38.6,46.6 34.3,48.5 30.6,52.2C26.9,55.9 25,60.2 25,65.3L25,289.7C25,306.4 28.3,323.1 34.8,339.5C41.3,356 49.4,370.6 59.1,383.3C68.7,396.1 80.2,408.5 93.6,420.5C106.9,432.6 119.3,442.6 130.6,450.6C141.9,458.6 153.7,466.1 166,473.2C178.3,480.3 187,485.1 192.2,487.7C197.4,490.2 201.5,492.2 204.6,493.5C206.9,494.7 209.5,495.3 212.2,495.3C214.9,495.3 217.5,494.7 219.8,493.5C222.9,492.1 227.1,490.2 232.2,487.7C237.4,485.2 246.1,480.3 258.4,473.2C270.7,466.1 282.5,458.5 293.8,450.6C305.1,442.6 317.4,432.6 330.8,420.5C344.1,408.4 355.6,396 365.3,383.3C374.9,370.5 383,355.9 389.5,339.5C396,323 399.3,306.4 399.3,289.7L399.3,65.3C399,60.2 397.1,55.9 393.4,52.2ZM350,291.8C350,373 212,443 212,443L212,94.6L350,94.6L350,291.8Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="nonZero"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 941 B |
12
src/Android/Resources/drawable/icon.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="1024dp"
|
||||
android:height="1024dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M1024,864c0,88.4 -71.6,160 -160,160H160C71.6,1024 0,952.4 0,864V160C0,71.6 71.6,0 160,0h704c88.4,0 160,71.6 160,160V864z"
|
||||
android:fillColor="#175DDC"/>
|
||||
<path
|
||||
android:pathData="M829.8,128.6c-6.5,-6.5 -14.2,-9.7 -23,-9.7H217.2c-8.9,0 -16.5,3.2 -23,9.7c-6.5,6.5 -9.7,14.2 -9.7,23v393.1c0,29.3 5.7,58.4 17.1,87.3c11.4,28.8 25.6,54.4 42.5,76.8c16.9,22.3 37,44.1 60.4,65.3c23.4,21.2 45,38.7 64.7,52.7c19.8,14 40.4,27.2 61.9,39.7c21.5,12.5 36.8,20.9 45.8,25.3c9,4.4 16.3,7.9 21.7,10.2c4.1,2 8.5,3.1 13.3,3.1c4.8,0 9.2,-1 13.3,-3.1c5.5,-2.4 12.7,-5.8 21.8,-10.2c9,-4.4 24.3,-12.9 45.8,-25.3c21.5,-12.5 42.1,-25.7 61.9,-39.7c19.8,-14 41.4,-31.6 64.8,-52.7c23.4,-21.2 43.5,-42.9 60.4,-65.3c16.9,-22.4 31,-47.9 42.5,-76.8c11.4,-28.8 17.1,-57.9 17.1,-87.3V151.7C839.6,142.8 836.3,135.1 829.8,128.6zM753.8,548.4c0,142.3 -241.8,264.9 -241.8,264.9V203.1h241.8C753.8,203.1 753.8,406.1 753.8,548.4z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.TabLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.google.android.material.tabs.TabLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/sliding_tabs"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<android.support.v7.widget.Toolbar
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 941 B |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 896 B After Width: | Height: | Size: 968 B |
|
Before Width: | Height: | Size: 608 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.8 KiB |
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Light theme -->
|
||||
<color name="colorPrimary">#3c8dbc</color>
|
||||
<color name="colorPrimaryDark">#222d32</color>
|
||||
<color name="primary">#3c8dbc</color>
|
||||
<color name="notificationBar">#3883af</color>
|
||||
<color name="colorPrimary">#175DDC</color>
|
||||
<color name="colorPrimaryDark">#1A3B66</color>
|
||||
<color name="primary">#175DDC</color>
|
||||
<color name="notificationBar">#1452BC</color>
|
||||
<color name="border">#dddddd</color>
|
||||
|
||||
<!-- Dark theme -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#3C8DBC</color>
|
||||
<color name="ic_launcher_background">#175DDC</color>
|
||||
</resources>
|
||||
@@ -16,4 +16,13 @@
|
||||
<string name="PasswordGenerator">
|
||||
Password Generator
|
||||
</string>
|
||||
<string name="AutoFillTile">
|
||||
Auto-fill
|
||||
</string>
|
||||
<string name="SelfHostedServerUrl">
|
||||
Self-hosted server URL
|
||||
</string>
|
||||
<string name="ServerUrl">
|
||||
Server URL
|
||||
</string>
|
||||
</resources>
|
||||
|
||||