Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caeadbc41e | ||
|
|
99143c0e3b | ||
|
|
1b145e38a3 | ||
|
|
f59cce15c0 | ||
|
|
7655c251a2 | ||
|
|
5608cb542f | ||
|
|
62add53c08 | ||
|
|
43bae6c05b | ||
|
|
55777d33ad | ||
|
|
0f5d14b589 | ||
|
|
00703d1570 | ||
|
|
fd03c33f4d | ||
|
|
c20f91b6d8 | ||
|
|
10b22e9e42 | ||
|
|
329f0871d5 | ||
|
|
66996f491c | ||
|
|
9ae39f3900 | ||
|
|
9d0db3c1e5 | ||
|
|
5932dd99ad | ||
|
|
910f0083cd | ||
|
|
32a8676572 | ||
|
|
801829ccbf | ||
|
|
b5107d21dd | ||
|
|
158bf873bd | ||
|
|
40cfb9876d | ||
|
|
12e3214f70 | ||
|
|
0eb68ec461 | ||
|
|
f231565163 | ||
|
|
7cca53bcc5 | ||
|
|
be94c94309 | ||
|
|
8e2d654b40 | ||
|
|
2ed5c0c5cc | ||
|
|
745ad3b9e9 | ||
|
|
3ce114760f | ||
|
|
bae7d1fc1d | ||
|
|
e4d9dfc128 | ||
|
|
7490ba3179 | ||
|
|
45da12ad55 | ||
|
|
75f99bf899 | ||
|
|
bd2b1cc166 | ||
|
|
5fa8d64994 | ||
|
|
034957b556 | ||
|
|
a9d7c73b04 | ||
|
|
b09fe05fe6 | ||
|
|
ed146549ef | ||
|
|
86c11db1a1 | ||
|
|
580cd57433 | ||
|
|
9854ce82bd | ||
|
|
af11df97f5 | ||
|
|
9b4e664908 | ||
|
|
567e1ee116 | ||
|
|
ef7fa5363a | ||
|
|
f8bd7c2e64 | ||
|
|
de46c9ee36 | ||
|
|
5969e2d7ed | ||
|
|
83047558d5 | ||
|
|
f239868299 | ||
|
|
e4b962a3a6 | ||
|
|
b68a94c0e5 | ||
|
|
ec53ca8423 | ||
|
|
1ba0729e34 | ||
|
|
73425c0052 | ||
|
|
679859fb37 | ||
|
|
dbdc660464 | ||
|
|
aa22e7e952 | ||
|
|
b920e7e95c | ||
|
|
d14b23ca82 | ||
|
|
4e8f69f692 | ||
|
|
c96cf2b0e5 | ||
|
|
4921cfb593 | ||
|
|
395545f7b1 | ||
|
|
f9d336a3a6 | ||
|
|
b32603b472 | ||
|
|
1124c48c8d | ||
|
|
98e429505c | ||
|
|
d0b616ba24 | ||
|
|
dac4ffcb98 | ||
|
|
680310cf70 | ||
|
|
67ff82810f | ||
|
|
87e71ea860 | ||
|
|
26c110291e | ||
|
|
9879f074b4 | ||
|
|
65168c71c0 | ||
|
|
4c4996ee2a | ||
|
|
e0c67f87b0 | ||
|
|
352c8ee867 | ||
|
|
fe5cc1f8f3 | ||
|
|
eec4be1845 | ||
|
|
2f86b5c7b0 | ||
|
|
0d672c4f99 | ||
|
|
ac3fdbc2cd | ||
|
|
0a7ad44d23 | ||
|
|
18a86d3f12 | ||
|
|
665e66a9a6 | ||
|
|
06dc4117c7 | ||
|
|
2651afcef0 | ||
|
|
ce4d828380 | ||
|
|
74fba486bd | ||
|
|
56075cb7d9 | ||
|
|
d71bc775d5 | ||
|
|
45c5801538 | ||
|
|
cf41b524b0 | ||
|
|
e2a3e55a17 | ||
|
|
ae35bd2047 | ||
|
|
2f0ca6f7c0 | ||
|
|
37428c01dd | ||
|
|
4116d95a3e | ||
|
|
35ae2b783f | ||
|
|
8a24a6d192 | ||
|
|
19374a5df4 | ||
|
|
12da6fbd18 | ||
|
|
573ff15925 | ||
|
|
1b2abbe321 | ||
|
|
4a03da6b96 | ||
|
|
cf3998942f | ||
|
|
0c71f783fc | ||
|
|
d30b30b24f | ||
|
|
7823ec3fc8 | ||
|
|
1e5883f028 | ||
|
|
33c3cf4c4f | ||
|
|
f41ace4d7c | ||
|
|
65d2d45a82 | ||
|
|
47ca483459 | ||
|
|
ee759af078 | ||
|
|
872037cf4d | ||
|
|
6aaa083157 | ||
|
|
6a88524f8e | ||
|
|
82d93d2602 | ||
|
|
d62037ef6a | ||
|
|
7314b5a339 | ||
|
|
62bc230521 | ||
|
|
3e0d34d148 | ||
|
|
aff1cc1cc3 | ||
|
|
6ddc7fa4cc | ||
|
|
957db1ec11 | ||
|
|
ae806da3f1 | ||
|
|
6a03c3e77d | ||
|
|
72b18eadf3 | ||
|
|
67aa583709 | ||
|
|
21f3755e44 | ||
|
|
c9b6df846e | ||
|
|
7e23a8169f | ||
|
|
b139eadf0b | ||
|
|
71ad648331 | ||
|
|
b8c7752356 | ||
|
|
b157f2085f | ||
|
|
2fda7b8011 | ||
|
|
5b24d19630 | ||
|
|
76652f6c6b | ||
|
|
724ae51110 | ||
|
|
1503124108 | ||
|
|
007125a071 | ||
|
|
b5f5b0b4aa | ||
|
|
cbda59e547 | ||
|
|
a885e16049 | ||
|
|
07eabad18d | ||
|
|
cf079a159f | ||
|
|
93176989fd |
@@ -15,4 +15,4 @@ artifacts:
|
||||
branches:
|
||||
except:
|
||||
- l10n_master
|
||||
image: Visual Studio 2017
|
||||
image: Previous Visual Studio 2017
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26430.13
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{04B18ED2-B76D-4947-8474-191F8FD2B5E0}"
|
||||
EndProject
|
||||
|
||||
@@ -103,19 +103,20 @@
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.9\lib\MonoAndroid10\FFImageLoading.Platform.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FormsViewGroup, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.267\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="HockeySDK, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\lib\MonoAndroid403\HockeySDK.dll</HintPath>
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.5\lib\MonoAndroid403\HockeySDK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="HockeySDK.AndroidBindings, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\lib\MonoAndroid403\HockeySDK.AndroidBindings.dll</HintPath>
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.5\lib\MonoAndroid403\HockeySDK.AndroidBindings.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Mono.Android" />
|
||||
<Reference Include="Mono.Android.Export" />
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
|
||||
@@ -125,46 +126,42 @@
|
||||
<HintPath>..\..\packages\PCLCrypto.2.0.147\lib\MonoAndroid23\PCLCrypto.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="PInvoke.BCrypt, Version=0.3.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.BCrypt.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.BCrypt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="PInvoke.BCrypt, Version=0.5.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.BCrypt.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.BCrypt.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PInvoke.Kernel32, Version=0.3.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.Kernel32.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Kernel32.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="PInvoke.Kernel32, Version=0.5.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.Kernel32.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.Kernel32.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PInvoke.NCrypt, Version=0.3.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.NCrypt.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.NCrypt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="PInvoke.NCrypt, Version=0.5.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.NCrypt.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.NCrypt.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PInvoke.Windows.Core, Version=0.3.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.Windows.Core.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Windows.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="PInvoke.Windows.Core, Version=0.5.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.Windows.Core.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.Windows.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Connectivity, Version=2.3.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\MonoAndroid10\Plugin.Connectivity.dll</HintPath>
|
||||
<Reference Include="Plugin.Connectivity, Version=3.0.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.3.0.2\lib\MonoAndroid10\Plugin.Connectivity.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Connectivity.Abstractions, Version=2.3.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll</HintPath>
|
||||
<Reference Include="Plugin.Connectivity.Abstractions, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.3.0.2\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.CurrentActivity, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.CurrentActivity.1.0.1\lib\MonoAndroid10\Plugin.CurrentActivity.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Fingerprint, Version=1.4.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.4\lib\MonoAndroid\Plugin.Fingerprint.dll</HintPath>
|
||||
<Reference Include="Plugin.Fingerprint, Version=1.4.5.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.5\lib\MonoAndroid\Plugin.Fingerprint.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Fingerprint.Abstractions, Version=1.4.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.4\lib\MonoAndroid\Plugin.Fingerprint.Abstractions.dll</HintPath>
|
||||
<Reference Include="Plugin.Fingerprint.Abstractions, Version=1.4.5.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.5\lib\MonoAndroid\Plugin.Fingerprint.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Fingerprint.Android.Samsung, Version=1.4.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.4\lib\MonoAndroid\Plugin.Fingerprint.Android.Samsung.dll</HintPath>
|
||||
<Reference Include="Plugin.Fingerprint.Android.Samsung, Version=1.4.5.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.5\lib\MonoAndroid\Plugin.Fingerprint.Android.Samsung.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Settings, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\MonoAndroid10\Plugin.Settings.dll</HintPath>
|
||||
<Reference Include="Plugin.Settings, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.3.0.1\lib\MonoAndroid10\Plugin.Settings.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\MonoAndroid10\Plugin.Settings.Abstractions.dll</HintPath>
|
||||
<Reference Include="Plugin.Settings.Abstractions, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.3.0.1\lib\MonoAndroid10\Plugin.Settings.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PushNotification.Plugin, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\MonoAndroid10\PushNotification.Plugin.dll</HintPath>
|
||||
@@ -174,48 +171,30 @@
|
||||
<HintPath>..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\MonoAndroid10\PushNotification.Plugin.Abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SimpleInjector, Version=4.0.7.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SimpleInjector.4.0.7\lib\netstandard1.3\SimpleInjector.dll</HintPath>
|
||||
<Reference Include="SimpleInjector, Version=4.0.8.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SimpleInjector.4.0.8\lib\netstandard1.3\SimpleInjector.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Splat.1.6.2\lib\monoandroid\Splat.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLite-net, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\sqlite-net-pcl.1.2.1\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLite-net.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCL.batteries, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCL.bundle_green.0.9.3\lib\MonoAndroid\SQLitePCL.batteries.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCL.raw, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCL.raw.0.9.3\lib\MonoAndroid\SQLitePCL.raw.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLPlugin_esqlite3, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCL.plugin.sqlite3.android.0.9.3\lib\MonoAndroid\SQLitePCLPlugin_esqlite3.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="SQLite-net, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\sqlite-net-pcl.1.4.118\lib\netstandard1.1\SQLite-net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.batteries_green, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a84b7dcfb1391f7f, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\MonoAndroid\SQLitePCLRaw.batteries_green.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\MonoAndroid\SQLitePCLRaw.batteries_green.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.batteries_v2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8226ea5df37bcae9, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\MonoAndroid\SQLitePCLRaw.batteries_v2.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\MonoAndroid\SQLitePCLRaw.batteries_v2.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.core.1.1.2\lib\MonoAndroid\SQLitePCLRaw.core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.core.1.1.8\lib\MonoAndroid\SQLitePCLRaw.core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.lib.e_sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e4ad490600e2234c, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.lib.e_sqlite3.android.1.1.2\lib\MonoAndroid\SQLitePCLRaw.lib.e_sqlite3.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.lib.e_sqlite3.android.1.1.8\lib\MonoAndroid\SQLitePCLRaw.lib.e_sqlite3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.provider.e_sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9c301db686d0bd12, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.provider.e_sqlite3.android.1.1.2\lib\MonoAndroid\SQLitePCLRaw.provider.e_sqlite3.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.provider.e_sqlite3.android.1.1.8\lib\MonoAndroid\SQLitePCLRaw.provider.e_sqlite3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -258,16 +237,16 @@
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\Xamarin.Forms.Core.dll</HintPath>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.267\lib\MonoAndroid10\Xamarin.Forms.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\Xamarin.Forms.Platform.dll</HintPath>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.267\lib\MonoAndroid10\Xamarin.Forms.Platform.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Platform.Android, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll</HintPath>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.267\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Xaml, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll</HintPath>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.267\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.GooglePlayServices.Analytics, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.GooglePlayServices.Analytics.29.0.0.2\lib\MonoAndroid41\Xamarin.GooglePlayServices.Analytics.dll</HintPath>
|
||||
@@ -296,12 +275,28 @@
|
||||
<Reference Include="XLabs.Ioc.SimpleInjector, Version=2.0.5782.12229, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\XLabs.IoC.SimpleInjector.2.0.5782\lib\portable-net45+netcore45+wp8+MonoAndroid1+MonoTouch1\XLabs.Ioc.SimpleInjector.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ZXing.Net.Mobile.Core, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ZXing.Net.Mobile.Forms, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Forms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ZXing.Net.Mobile.Forms.Android, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Forms.Android.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="zxing.portable, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\zxing.portable.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ZXingNetMobile, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\ZXingNetMobile.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AutofillActivity.cs" />
|
||||
<Compile Include="AutofillCredentials.cs" />
|
||||
<Compile Include="Controls\CustomSearchBarRenderer.cs" />
|
||||
<Compile Include="Controls\CustomButtonRenderer.cs" />
|
||||
<Compile Include="Controls\HybridWebViewRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedButtonRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedTabbedPageRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedTableViewRenderer.cs" />
|
||||
@@ -314,14 +309,14 @@
|
||||
<Compile Include="Controls\ExtendedPickerRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedEntryRenderer.cs" />
|
||||
<Compile Include="Services\HttpService.cs" />
|
||||
<Compile Include="Services\KeyStoreBackedStorageService.cs" />
|
||||
<Compile Include="Services\AndroidKeyStoreStorageService.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
<Compile Include="MainApplication.cs" />
|
||||
<Compile Include="Resources\Resource.Designer.cs" />
|
||||
<Compile Include="Services\DeviceInfoService.cs" />
|
||||
<Compile Include="Services\GoogleAnalyticsService.cs" />
|
||||
<Compile Include="Services\AppInfoService.cs" />
|
||||
<Compile Include="Services\ClipboardService.cs" />
|
||||
<Compile Include="Services\DeviceActionService.cs" />
|
||||
<Compile Include="Services\BouncyCastleKeyDerivationService.cs" />
|
||||
<Compile Include="Services\KeyStoreStorageService.cs" />
|
||||
<Compile Include="MainActivity.cs" />
|
||||
@@ -331,6 +326,8 @@
|
||||
<Compile Include="Services\ReflectionService.cs" />
|
||||
<Compile Include="Services\SqlService.cs" />
|
||||
<Compile Include="SplashActivity.cs" />
|
||||
<Compile Include="PackageReplacedReceiver.cs" />
|
||||
<Compile Include="Utilities.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="8bit.keystore.enc" />
|
||||
@@ -857,6 +854,96 @@
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\share_tools.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\xml\filepaths.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\download.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\download.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\download.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\download.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\download.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\camera.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\camera.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\camera.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\camera.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\camera.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\paperclip.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\paperclip.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\paperclip.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\paperclip.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\paperclip.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\trash.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\trash.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\trash.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\trash.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\trash.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\cog.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\cog.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
@@ -865,10 +952,10 @@
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.4.267\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.4.267\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets" Condition="Exists('..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets')" />
|
||||
<Import Project="..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
|
||||
<Import Project="..\..\packages\Xamarin.Forms.2.3.4.267\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.4.267\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -36,12 +36,13 @@ namespace Bit.Android
|
||||
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.sec.android.app.sbrowser", "sbrowser_url_bar"),
|
||||
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",
|
||||
(s) => s.Split(' ').FirstOrDefault()),
|
||||
new Browser("org.mozilla.firefox", "url_bar_title"),
|
||||
new Browser("org.mozilla.firefox_beta", "url_bar_title"),
|
||||
new Browser("org.mozilla.focus", "display_url"),
|
||||
new Browser("com.ghostery.android.ghostery", "search_field"),
|
||||
new Browser("org.adblockplus.browser", "url_bar_title"),
|
||||
new Browser("com.htc.sense.browser", "title"),
|
||||
@@ -52,7 +53,9 @@ namespace Bit.Android
|
||||
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("com.ksmobile.cb", "address_bar_edit_text"),
|
||||
new Browser("acr.browser.lightning", "search"),
|
||||
new Browser("acr.browser.barebones", "search")
|
||||
}.ToDictionary(n => n.PackageName);
|
||||
|
||||
private readonly IAppSettingsService _appSettings;
|
||||
@@ -66,6 +69,12 @@ namespace Bit.Android
|
||||
|
||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||
{
|
||||
var powerManager = (PowerManager)GetSystemService(PowerService);
|
||||
if(!powerManager.IsInteractive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var root = RootInActiveWindow;
|
||||
@@ -75,11 +84,9 @@ namespace Bit.Android
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
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 });
|
||||
testNodes.Dispose();
|
||||
*/
|
||||
//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 });
|
||||
//testNodes.Dispose();
|
||||
|
||||
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||
var cancelNotification = true;
|
||||
@@ -218,9 +225,18 @@ namespace Bit.Android
|
||||
cancelNotification = false;
|
||||
}
|
||||
}
|
||||
|
||||
AutofillActivity.LastCredentials = null;
|
||||
}
|
||||
else if(AutofillActivity.LastCredentials != null)
|
||||
{
|
||||
System.Threading.Tasks.Task.Run(async () =>
|
||||
{
|
||||
await System.Threading.Tasks.Task.Delay(1000);
|
||||
AutofillActivity.LastCredentials = null;
|
||||
});
|
||||
}
|
||||
|
||||
AutofillActivity.LastCredentials = null;
|
||||
passwordNodes.Dispose();
|
||||
return cancelNotification;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,11 @@ namespace Bit.Android.Controls
|
||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextFlagNoSuggestions);
|
||||
}
|
||||
|
||||
if(_view.IsPassword)
|
||||
{
|
||||
Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
|
||||
}
|
||||
|
||||
_view.ToggleIsPassword += ToggleIsPassword;
|
||||
|
||||
if(_view.FontFamily == "monospace")
|
||||
@@ -69,6 +74,7 @@ namespace Bit.Android.Controls
|
||||
var cursorEnd = Control.SelectionEnd;
|
||||
|
||||
Control.TransformationMethod = _isPassword ? null : new PasswordTransformationMethod();
|
||||
Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
|
||||
|
||||
// set focus
|
||||
Control.RequestFocus();
|
||||
@@ -85,11 +91,9 @@ namespace Bit.Android.Controls
|
||||
}
|
||||
|
||||
// show keyboard
|
||||
var app = XLabs.Ioc.Resolver.Resolve<global::Android.App.Application>();
|
||||
var inputMethodManager =
|
||||
app.GetSystemService(global::Android.Content.Context.InputMethodService) as InputMethodManager;
|
||||
inputMethodManager.ShowSoftInput(Control, ShowFlags.Forced);
|
||||
inputMethodManager.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
|
||||
var imm = Forms.Context.GetSystemService(global::Android.Content.Context.InputMethodService) as InputMethodManager;
|
||||
imm.ShowSoftInput(Control, ShowFlags.Forced);
|
||||
imm.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
|
||||
|
||||
_isPassword = _view.IsPasswordFromToggled = !_isPassword;
|
||||
_toggledPassword = true;
|
||||
|
||||
72
src/Android/Controls/HybridWebViewRenderer.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Android.Webkit;
|
||||
using AWebkit = Android.Webkit;
|
||||
using Java.Interop;
|
||||
|
||||
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
|
||||
{
|
||||
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if(Control == null)
|
||||
{
|
||||
var webView = new AWebkit.WebView(Forms.Context);
|
||||
webView.Settings.JavaScriptEnabled = true;
|
||||
SetNativeControl(webView);
|
||||
}
|
||||
|
||||
if(e.OldElement != null)
|
||||
{
|
||||
Control.RemoveJavascriptInterface("jsBridge");
|
||||
var hybridWebView = e.OldElement as HybridWebView;
|
||||
hybridWebView.Cleanup();
|
||||
}
|
||||
|
||||
if(e.NewElement != null)
|
||||
{
|
||||
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
|
||||
Control.LoadUrl(Element.Uri);
|
||||
InjectJS(JSFunction);
|
||||
}
|
||||
}
|
||||
|
||||
private void InjectJS(string script)
|
||||
{
|
||||
if(Control != null)
|
||||
{
|
||||
Control.LoadUrl(string.Format("javascript: {0}", script));
|
||||
}
|
||||
}
|
||||
|
||||
public class JSBridge : Java.Lang.Object
|
||||
{
|
||||
private readonly WeakReference<HybridWebViewRenderer> _hybridWebViewRenderer;
|
||||
|
||||
public JSBridge(HybridWebViewRenderer hybridRenderer)
|
||||
{
|
||||
_hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
|
||||
}
|
||||
|
||||
[JavascriptInterface]
|
||||
[Export("invokeAction")]
|
||||
public void InvokeAction(string data)
|
||||
{
|
||||
HybridWebViewRenderer hybridRenderer;
|
||||
if(_hybridWebViewRenderer != null && _hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
|
||||
{
|
||||
hybridRenderer.Element.InvokeAction(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,16 +14,24 @@ using Xamarin.Forms.Platform.Android;
|
||||
using Xamarin.Forms;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App;
|
||||
using Android.Nfc;
|
||||
using Android.Views.InputMethods;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Activity(Label = "bitwarden",
|
||||
Icon = "@drawable/icon",
|
||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
|
||||
WindowSoftInputMode = SoftInput.StateHidden)]
|
||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
|
||||
public class MainActivity : FormsAppCompatActivity
|
||||
{
|
||||
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
|
||||
private DateTime? _lastAction;
|
||||
private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private ISettings _settings;
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
@@ -46,8 +54,10 @@ namespace Bit.Android
|
||||
Task.Delay(10).Wait();
|
||||
|
||||
Console.WriteLine("A OnCreate");
|
||||
Window.SetSoftInputMode(SoftInput.StateHidden);
|
||||
Window.AddFlags(WindowManagerFlags.Secure);
|
||||
if(!App.Utilities.Helpers.InDebugMode())
|
||||
{
|
||||
Window.AddFlags(WindowManagerFlags.Secure);
|
||||
}
|
||||
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
var authService = Resolver.Resolve<IAuthService>();
|
||||
@@ -60,6 +70,8 @@ namespace Bit.Android
|
||||
typeof(Color).GetProperty("Accent", BindingFlags.Public | BindingFlags.Static)
|
||||
.SetValue(null, Color.FromHex("d2d6de"));
|
||||
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
LoadApplication(new App.App(
|
||||
uri,
|
||||
Resolver.Resolve<IAuthService>(),
|
||||
@@ -67,12 +79,19 @@ namespace Bit.Android
|
||||
Resolver.Resolve<IUserDialogs>(),
|
||||
Resolver.Resolve<IDatabaseService>(),
|
||||
Resolver.Resolve<ISyncService>(),
|
||||
Resolver.Resolve<ISettings>(),
|
||||
_settings,
|
||||
Resolver.Resolve<ILockService>(),
|
||||
Resolver.Resolve<IGoogleAnalyticsService>(),
|
||||
Resolver.Resolve<ILocalizeService>(),
|
||||
Resolver.Resolve<IAppInfoService>(),
|
||||
Resolver.Resolve<IAppSettingsService>()));
|
||||
Resolver.Resolve<IAppSettingsService>(),
|
||||
_deviceActionService));
|
||||
|
||||
MessagingCenter.Subscribe<Xamarin.Forms.Application>(
|
||||
Xamarin.Forms.Application.Current, "DismissKeyboard", (sender) =>
|
||||
{
|
||||
DismissKeyboard();
|
||||
});
|
||||
|
||||
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, "RateApp", (sender) =>
|
||||
{
|
||||
@@ -94,6 +113,18 @@ namespace Bit.Android
|
||||
{
|
||||
MoveTaskToBack(true);
|
||||
});
|
||||
|
||||
MessagingCenter.Subscribe<Xamarin.Forms.Application, string>(
|
||||
Xamarin.Forms.Application.Current, "LaunchApp", (sender, args) =>
|
||||
{
|
||||
LaunchApp(args);
|
||||
});
|
||||
|
||||
MessagingCenter.Subscribe<Xamarin.Forms.Application, bool>(
|
||||
Xamarin.Forms.Application.Current, "ListenYubiKeyOTP", (sender, listen) =>
|
||||
{
|
||||
ListenYubiKey(listen);
|
||||
});
|
||||
}
|
||||
|
||||
private void ReturnCredentials(VaultListPageModel.Login login)
|
||||
@@ -105,6 +136,13 @@ namespace Bit.Android
|
||||
}
|
||||
else
|
||||
{
|
||||
var isPremium = Resolver.Resolve<ITokenService>()?.TokenPremium ?? false;
|
||||
var autoCopyEnabled = !_settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false);
|
||||
if(isPremium && autoCopyEnabled && _deviceActionService != null && login.Totp.Value != null)
|
||||
{
|
||||
_deviceActionService.CopyToClipboard(App.Utilities.Crypto.Totp(login.Totp.Value));
|
||||
}
|
||||
|
||||
data.PutExtra("uri", login.Uri.Value);
|
||||
data.PutExtra("username", login.Username);
|
||||
data.PutExtra("password", login.Password.Value);
|
||||
@@ -118,7 +156,7 @@ namespace Bit.Android
|
||||
{
|
||||
Parent.SetResult(Result.Ok, data);
|
||||
}
|
||||
|
||||
|
||||
Finish();
|
||||
}
|
||||
|
||||
@@ -126,6 +164,7 @@ namespace Bit.Android
|
||||
{
|
||||
Console.WriteLine("A OnPause");
|
||||
base.OnPause();
|
||||
ListenYubiKey(false);
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
@@ -160,6 +199,68 @@ namespace Bit.Android
|
||||
// workaround for app compat bug
|
||||
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
|
||||
Task.Delay(10).Wait();
|
||||
|
||||
if(Utilities.NfcEnabled())
|
||||
{
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ResumeYubiKey");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
{
|
||||
base.OnNewIntent(intent);
|
||||
Console.WriteLine("A OnNewIntent");
|
||||
ParseYubiKey(intent.DataString);
|
||||
}
|
||||
|
||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
||||
{
|
||||
if(requestCode == Constants.SelectFilePermissionRequestCode)
|
||||
{
|
||||
if(grantResults.Any(r => r != Permission.Granted))
|
||||
{
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileCameraPermissionDenied");
|
||||
}
|
||||
await _deviceActionService.SelectFileAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||
{
|
||||
if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok)
|
||||
{
|
||||
global::Android.Net.Uri uri = null;
|
||||
string fileName = null;
|
||||
if(data != null && data.Data != null)
|
||||
{
|
||||
uri = data.Data;
|
||||
fileName = Utilities.GetFileName(ApplicationContext, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// camera
|
||||
var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
|
||||
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
|
||||
uri = global::Android.Net.Uri.FromFile(file);
|
||||
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
||||
}
|
||||
|
||||
if(uri == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using(var stream = ContentResolver.OpenInputStream(uri))
|
||||
using(var memoryStream = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(memoryStream);
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileResult",
|
||||
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RateApp()
|
||||
@@ -199,5 +300,80 @@ namespace Bit.Android
|
||||
var intent = new Intent(global::Android.Provider.Settings.ActionAccessibilitySettings);
|
||||
StartActivity(intent);
|
||||
}
|
||||
|
||||
private void LaunchApp(string packageName)
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
packageName = packageName.Replace("androidapp://", string.Empty);
|
||||
var launchIntent = PackageManager.GetLaunchIntentForPackage(packageName);
|
||||
if(launchIntent == null)
|
||||
{
|
||||
var dialog = Resolver.Resolve<IUserDialogs>();
|
||||
dialog.Alert(string.Format(App.Resources.AppResources.CannotOpenApp, packageName));
|
||||
}
|
||||
else
|
||||
{
|
||||
StartActivity(launchIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private void ListenYubiKey(bool listen)
|
||||
{
|
||||
if(!Utilities.NfcEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
||||
if(listen)
|
||||
{
|
||||
var intent = new Intent(this, Class);
|
||||
intent.AddFlags(ActivityFlags.SingleTop);
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
||||
|
||||
// register for all NDEF tags starting with http och https
|
||||
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
||||
ndef.AddDataScheme("http");
|
||||
ndef.AddDataScheme("https");
|
||||
var filters = new IntentFilter[] { ndef };
|
||||
|
||||
// register for foreground dispatch so we'll receive tags according to our intent filters
|
||||
adapter.EnableForegroundDispatch(this, pendingIntent, filters, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
adapter.DisableForegroundDispatch(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseYubiKey(string data)
|
||||
{
|
||||
if(data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var otpMatch = _otpPattern.Matcher(data);
|
||||
if(otpMatch.Matches())
|
||||
{
|
||||
var otp = otpMatch.Group(1);
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "GotYubiKeyOTP", otp);
|
||||
}
|
||||
}
|
||||
|
||||
private void DismissKeyboard()
|
||||
{
|
||||
try
|
||||
{
|
||||
var imm = (InputMethodManager)GetSystemService(InputMethodService);
|
||||
imm.HideSoftInputFromWindow(CurrentFocus.WindowToken, 0);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,21 @@ namespace Bit.Android
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership transer)
|
||||
: base(handle, transer)
|
||||
{
|
||||
//AndroidEnvironment.UnhandledExceptionRaiser += AndroidEnvironment_UnhandledExceptionRaiser;
|
||||
|
||||
if(!Resolver.IsSet)
|
||||
{
|
||||
SetIoc(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void AndroidEnvironment_UnhandledExceptionRaiser(object sender, RaiseThrowableEventArgs e)
|
||||
{
|
||||
var message = Utilities.AppendExceptionToMessage("", e.Exception);
|
||||
//Utilities.SaveCrashFile(message, true);
|
||||
Utilities.SendCrashEmail(message, false);
|
||||
}
|
||||
|
||||
public override void OnCreate()
|
||||
{
|
||||
base.OnCreate();
|
||||
@@ -84,7 +93,7 @@ namespace Bit.Android
|
||||
}
|
||||
|
||||
// 3. In debug mode
|
||||
if(InDebugMode())
|
||||
if(App.Utilities.Helpers.InDebugMode())
|
||||
{
|
||||
reregister = true;
|
||||
}
|
||||
@@ -105,15 +114,6 @@ namespace Bit.Android
|
||||
}
|
||||
}
|
||||
|
||||
private bool InDebugMode()
|
||||
{
|
||||
#if DEBUG
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void OnTerminate()
|
||||
{
|
||||
base.OnTerminate();
|
||||
@@ -179,6 +179,7 @@ namespace Bit.Android
|
||||
{
|
||||
UserDialogs.Init(application);
|
||||
CachedImageRenderer.Init();
|
||||
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
||||
|
||||
//var container = new UnityContainer();
|
||||
@@ -191,14 +192,14 @@ namespace Bit.Android
|
||||
// Services
|
||||
container.RegisterSingleton<IDatabaseService, DatabaseService>();
|
||||
container.RegisterSingleton<ISqlService, SqlService>();
|
||||
container.RegisterSingleton<ISecureStorageService, KeyStoreBackedStorageService>();
|
||||
container.RegisterSingleton<ISecureStorageService, AndroidKeyStoreStorageService>();
|
||||
container.RegisterSingleton<ICryptoService, CryptoService>();
|
||||
container.RegisterSingleton<IKeyDerivationService, BouncyCastleKeyDerivationService>();
|
||||
container.RegisterSingleton<IAuthService, AuthService>();
|
||||
container.RegisterSingleton<IFolderService, FolderService>();
|
||||
container.RegisterSingleton<ILoginService, LoginService>();
|
||||
container.RegisterSingleton<ISyncService, SyncService>();
|
||||
container.RegisterSingleton<IClipboardService, ClipboardService>();
|
||||
container.RegisterSingleton<IDeviceActionService, DeviceActionService>();
|
||||
container.RegisterSingleton<IAppIdService, AppIdService>();
|
||||
container.RegisterSingleton<IPasswordGenerationService, PasswordGenerationService>();
|
||||
container.RegisterSingleton<IReflectionService, ReflectionService>();
|
||||
@@ -218,6 +219,7 @@ namespace Bit.Android
|
||||
container.RegisterSingleton<IFolderRepository, FolderRepository>();
|
||||
container.RegisterSingleton<IFolderApiRepository, FolderApiRepository>();
|
||||
container.RegisterSingleton<ILoginRepository, LoginRepository>();
|
||||
container.RegisterSingleton<IAttachmentRepository, AttachmentRepository>();
|
||||
container.RegisterSingleton<ILoginApiRepository, LoginApiRepository>();
|
||||
container.RegisterSingleton<IConnectApiRepository, ConnectApiRepository>();
|
||||
container.RegisterSingleton<IDeviceApiRepository, DeviceApiRepository>();
|
||||
@@ -225,6 +227,7 @@ namespace Bit.Android
|
||||
container.RegisterSingleton<ICipherApiRepository, CipherApiRepository>();
|
||||
container.RegisterSingleton<ISettingsRepository, SettingsRepository>();
|
||||
container.RegisterSingleton<ISettingsApiRepository, SettingsApiRepository>();
|
||||
container.RegisterSingleton<ITwoFactorApiRepository, TwoFactorApiRepository>();
|
||||
|
||||
// Other
|
||||
container.RegisterSingleton(CrossSettings.Current);
|
||||
|
||||
22
src/Android/PackageReplacedReceiver.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using System.Diagnostics;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.PackageReplacedReceiver", Exported = true)]
|
||||
[IntentFilter(new[] { Intent.ActionMyPackageReplaced })]
|
||||
public class PackageReplacedReceiver : BroadcastReceiver
|
||||
{
|
||||
public override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
Debug.WriteLine("App updated!");
|
||||
Helpers.PerformUpdateTasks(Resolver.Resolve<ISettings>(), Resolver.Resolve<IAppInfoService>(),
|
||||
Resolver.Resolve<IDatabaseService>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.6.0" android:installLocation="auto" android:versionCode="502">
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
<uses-permission android:name="com.x8bit.bitwarden.permission.C2D_MESSAGE" />
|
||||
<application android:label="bitwarden" android:theme="@style/BitwardenTheme"></application>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.9.0" android:installLocation="auto" android:versionCode="502" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
|
||||
|
||||
<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="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
<uses-permission android:name="com.x8bit.bitwarden.permission.C2D_MESSAGE" />
|
||||
|
||||
<application android:label="bitwarden" android:theme="@style/BitwardenTheme" android:allowBackup="false">
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.x8bit.bitwarden.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -28,7 +28,3 @@ using Android.App;
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
||||
// Add some common permissions, these can be removed if not needed
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
|
||||
518
src/Android/Resources/Resource.Designer.cs
generated
BIN
src/Android/Resources/drawable-hdpi/camera.png
Normal file
|
After Width: | Height: | Size: 664 B |
BIN
src/Android/Resources/drawable-hdpi/cog.png
Normal file
|
After Width: | Height: | Size: 883 B |
BIN
src/Android/Resources/drawable-hdpi/download.png
Normal file
|
After Width: | Height: | Size: 467 B |
BIN
src/Android/Resources/drawable-hdpi/paperclip.png
Normal file
|
After Width: | Height: | Size: 658 B |
BIN
src/Android/Resources/drawable-hdpi/trash.png
Normal file
|
After Width: | Height: | Size: 452 B |
BIN
src/Android/Resources/drawable-hdpi/yubikey.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
src/Android/Resources/drawable-xhdpi/camera.png
Normal file
|
After Width: | Height: | Size: 802 B |
BIN
src/Android/Resources/drawable-xhdpi/cog.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/Android/Resources/drawable-xhdpi/download.png
Normal file
|
After Width: | Height: | Size: 556 B |
BIN
src/Android/Resources/drawable-xhdpi/paperclip.png
Normal file
|
After Width: | Height: | Size: 802 B |
BIN
src/Android/Resources/drawable-xhdpi/trash.png
Normal file
|
After Width: | Height: | Size: 390 B |
BIN
src/Android/Resources/drawable-xhdpi/yubikey.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
src/Android/Resources/drawable-xxhdpi/camera.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/Android/Resources/drawable-xxhdpi/cog.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/Android/Resources/drawable-xxhdpi/download.png
Normal file
|
After Width: | Height: | Size: 707 B |
BIN
src/Android/Resources/drawable-xxhdpi/paperclip.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/Android/Resources/drawable-xxhdpi/trash.png
Normal file
|
After Width: | Height: | Size: 534 B |
BIN
src/Android/Resources/drawable-xxhdpi/yubikey.png
Normal file
|
After Width: | Height: | Size: 381 KiB |
BIN
src/Android/Resources/drawable-xxxhdpi/camera.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/Android/Resources/drawable-xxxhdpi/cog.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/Android/Resources/drawable-xxxhdpi/download.png
Normal file
|
After Width: | Height: | Size: 690 B |
BIN
src/Android/Resources/drawable-xxxhdpi/paperclip.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/Android/Resources/drawable-xxxhdpi/trash.png
Normal file
|
After Width: | Height: | Size: 552 B |
BIN
src/Android/Resources/drawable/camera.png
Normal file
|
After Width: | Height: | Size: 478 B |
BIN
src/Android/Resources/drawable/cog.png
Normal file
|
After Width: | Height: | Size: 610 B |
BIN
src/Android/Resources/drawable/download.png
Normal file
|
After Width: | Height: | Size: 378 B |
BIN
src/Android/Resources/drawable/paperclip.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
src/Android/Resources/drawable/trash.png
Normal file
|
After Width: | Height: | Size: 274 B |
BIN
src/Android/Resources/drawable/yubikey.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
4
src/Android/Resources/xml/filepaths.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="cache" path="." />
|
||||
</paths>
|
||||
370
src/Android/Services/AndroidKeyStoreStorageService.cs
Normal file
@@ -0,0 +1,370 @@
|
||||
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 Plugin.Settings.Abstractions;
|
||||
using Java.Util;
|
||||
using Javax.Crypto.Spec;
|
||||
using Android.Preferences;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
public class AndroidKeyStoreStorageService : ISecureStorageService
|
||||
{
|
||||
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 ISettings _settings;
|
||||
private readonly KeyStore _keyStore;
|
||||
private readonly ISecureStorageService _oldKeyStorageService;
|
||||
|
||||
public AndroidKeyStoreStorageService(ISettings settings)
|
||||
{
|
||||
_oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
|
||||
_rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
|
||||
|
||||
_oldKeyStorageService = new KeyStoreStorageService(new char[] { });
|
||||
_settings = settings;
|
||||
|
||||
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
|
||||
_keyStore.Load(null);
|
||||
|
||||
GenerateStoreKey();
|
||||
GenerateAesKey();
|
||||
}
|
||||
|
||||
public bool Contains(string key)
|
||||
{
|
||||
return _settings.Contains(string.Format(SettingsFormat, key)) ||
|
||||
_settings.Contains(string.Format(SettingsFormatV1, key)) ||
|
||||
_oldKeyStorageService.Contains(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.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKey);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
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.Utilities.Crypto.AesCbcEncrypt(dataBytes, aesKey);
|
||||
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Console.WriteLine("Failed to encrypt to secure storage.");
|
||||
//Utilities.SendCrashEmail(e);
|
||||
//Utilities.SaveCrashFile(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateStoreKey()
|
||||
{
|
||||
if(_keyStore.ContainsAlias(KeyAlias))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearSettings();
|
||||
|
||||
var end = Calendar.Instance;
|
||||
end.Add(CalendarField.Year, 99);
|
||||
|
||||
if(_oldAndroid)
|
||||
{
|
||||
var subject = new X500Principal($"CN={KeyAlias}");
|
||||
|
||||
var spec = new KeyPairGeneratorSpec.Builder(Application.Context)
|
||||
.SetAlias(KeyAlias)
|
||||
.SetSubject(subject)
|
||||
.SetSerialNumber(BigInteger.Ten)
|
||||
.SetStartDate(new Date(0))
|
||||
.SetEndDate(end.Time)
|
||||
.Build();
|
||||
|
||||
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
|
||||
gen.Initialize(spec);
|
||||
gen.GenerateKeyPair();
|
||||
}
|
||||
else
|
||||
{
|
||||
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
||||
.SetBlockModes(KeyProperties.BlockModeGcm)
|
||||
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
|
||||
.SetKeyValidityStart(new Date(0))
|
||||
.SetKeyValidityEnd(end.Time)
|
||||
.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.Utilities.Crypto.RandomBytes(512 / 8);
|
||||
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
|
||||
_settings.AddOrUpdateValue(AesKey, encKey);
|
||||
}
|
||||
|
||||
private App.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.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.Models.SymmetricCryptoKey(key);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if(_oldKeyStorageService.Contains(key))
|
||||
{
|
||||
var value = _oldKeyStorageService.Retrieve(key);
|
||||
Store(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
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.Utilities.Crypto.AesCbcDecrypt(new App.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)
|
||||
{
|
||||
if(_oldKeyStorageService.Contains(key))
|
||||
{
|
||||
_oldKeyStorageService.Delete(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
public class ClipboardService : IClipboardService
|
||||
{
|
||||
public void CopyToClipboard(string text)
|
||||
{
|
||||
var clipboardManager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
|
||||
clipboardManager.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
229
src/Android/Services/DeviceActionService.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using Android.Webkit;
|
||||
using Plugin.CurrentActivity;
|
||||
using System.IO;
|
||||
using Android.Support.V4.Content;
|
||||
using Bit.App;
|
||||
using Bit.App.Resources;
|
||||
using Android.Provider;
|
||||
using System.Threading.Tasks;
|
||||
using Android.OS;
|
||||
using System.Collections.Generic;
|
||||
using Android;
|
||||
using Android.Content.PM;
|
||||
using Android.Support.V4.App;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
public class DeviceActionService : IDeviceActionService
|
||||
{
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
private bool _cameraPermissionsDenied;
|
||||
|
||||
public DeviceActionService(
|
||||
IAppSettingsService appSettingsService)
|
||||
{
|
||||
_appSettingsService = appSettingsService;
|
||||
}
|
||||
|
||||
public void CopyToClipboard(string text)
|
||||
{
|
||||
var clipboardManager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
|
||||
clipboardManager.Text = text;
|
||||
}
|
||||
|
||||
public bool OpenFile(byte[] fileData, string id, string fileName)
|
||||
{
|
||||
if(!CanOpenFile(fileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||
if(extension == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
||||
if(mimeType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var cachePath = CrossCurrentActivity.Current.Activity.CacheDir;
|
||||
var filePath = Path.Combine(cachePath.Path, fileName);
|
||||
File.WriteAllBytes(filePath, fileData);
|
||||
var file = new Java.IO.File(cachePath, fileName);
|
||||
if(!file.IsFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var intent = new Intent(Intent.ActionView);
|
||||
var uri = FileProvider.GetUriForFile(CrossCurrentActivity.Current.Activity.ApplicationContext,
|
||||
"com.x8bit.bitwarden.fileprovider", file);
|
||||
intent.SetDataAndType(uri, mimeType);
|
||||
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
|
||||
CrossCurrentActivity.Current.Activity.StartActivity(intent);
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanOpenFile(string fileName)
|
||||
{
|
||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||
if(extension == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
||||
if(mimeType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
|
||||
var intent = new Intent(Intent.ActionView);
|
||||
intent.SetType(mimeType);
|
||||
var activities = pm.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly);
|
||||
return (activities?.Count ?? 0) > 0;
|
||||
}
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
|
||||
_appSettingsService.LastCacheClear = DateTime.UtcNow;
|
||||
}
|
||||
catch(Exception) { }
|
||||
}
|
||||
|
||||
private bool DeleteDir(Java.IO.File dir)
|
||||
{
|
||||
if(dir != null && dir.IsDirectory)
|
||||
{
|
||||
var children = dir.List();
|
||||
for(int i = 0; i < children.Length; i++)
|
||||
{
|
||||
var success = DeleteDir(new Java.IO.File(dir, children[i]));
|
||||
if(!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return dir.Delete();
|
||||
}
|
||||
else if(dir != null && dir.IsFile)
|
||||
{
|
||||
return dir.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task SelectFileAsync()
|
||||
{
|
||||
MessagingCenter.Unsubscribe<Application>(Application.Current, "SelectFileCameraPermissionDenied");
|
||||
|
||||
var hasStorageWritePermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.WriteExternalStorage);
|
||||
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
|
||||
|
||||
if(!_cameraPermissionsDenied && !hasStorageWritePermission)
|
||||
{
|
||||
AskCameraPermission(Manifest.Permission.WriteExternalStorage);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if(!_cameraPermissionsDenied && !hasCameraPermission)
|
||||
{
|
||||
AskCameraPermission(Manifest.Permission.Camera);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
var additionalIntents = new List<IParcelable>();
|
||||
|
||||
var docIntent = new Intent(Intent.ActionOpenDocument);
|
||||
docIntent.AddCategory(Intent.CategoryOpenable);
|
||||
docIntent.SetType("*/*");
|
||||
|
||||
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
|
||||
|
||||
if(!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
|
||||
{
|
||||
try
|
||||
{
|
||||
var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
|
||||
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
|
||||
if(!file.Exists())
|
||||
{
|
||||
file.ParentFile.Mkdirs();
|
||||
file.CreateNewFile();
|
||||
}
|
||||
var outputFileUri = global::Android.Net.Uri.FromFile(file);
|
||||
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
|
||||
}
|
||||
catch(Java.IO.IOException) { }
|
||||
}
|
||||
|
||||
if(additionalIntents.Count > 0)
|
||||
{
|
||||
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
|
||||
}
|
||||
|
||||
CrossCurrentActivity.Current.Activity.StartActivityForResult(chooserIntent, Constants.SelectFileRequestCode);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
private List<IParcelable> GetCameraIntents(global::Android.Net.Uri outputUri)
|
||||
{
|
||||
var intents = new List<IParcelable>();
|
||||
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
|
||||
var captureIntent = new Intent(MediaStore.ActionImageCapture);
|
||||
var listCam = pm.QueryIntentActivities(captureIntent, 0);
|
||||
foreach(var res in listCam)
|
||||
{
|
||||
var packageName = res.ActivityInfo.PackageName;
|
||||
var intent = new Intent(captureIntent);
|
||||
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
|
||||
intent.SetPackage(packageName);
|
||||
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
|
||||
intents.Add(intent);
|
||||
}
|
||||
return intents;
|
||||
}
|
||||
|
||||
private bool HasPermission(string permission)
|
||||
{
|
||||
return ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
|
||||
}
|
||||
|
||||
private void AskCameraPermission(string permission)
|
||||
{
|
||||
MessagingCenter.Subscribe<Application>(Application.Current, "SelectFileCameraPermissionDenied", (sender) =>
|
||||
{
|
||||
_cameraPermissionsDenied = true;
|
||||
});
|
||||
|
||||
AskPermission(permission);
|
||||
}
|
||||
|
||||
private void AskPermission(string permission)
|
||||
{
|
||||
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
|
||||
Constants.SelectFilePermissionRequestCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Android.App;
|
||||
using Android.OS;
|
||||
using Android.Util;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
@@ -42,5 +41,6 @@ namespace Bit.Android.Services
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
public bool NfcEnabled => Utilities.NfcEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
using System.IO;
|
||||
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 Plugin.Settings.Abstractions;
|
||||
using Javax.Crypto.Spec;
|
||||
using System.Collections.Generic;
|
||||
using Java.Util;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
public class KeyStoreBackedStorageService : ISecureStorageService
|
||||
{
|
||||
private const string AndroidKeyStore = "AndroidKeyStore";
|
||||
private const string AndroidOpenSSL = "AndroidOpenSSL";
|
||||
private const string KeyAlias = "bitwardenKey";
|
||||
private const string SettingsFormat = "ksSecured:{0}";
|
||||
private const string RsaMode = "RSA/ECB/PKCS1Padding";
|
||||
private const string AesMode = "AES/GCM/NoPadding";
|
||||
private const string AesKey = "ksSecured:aesKeyForService";
|
||||
|
||||
private readonly ISettings _settings;
|
||||
private readonly KeyStore _keyStore;
|
||||
private readonly bool _oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
|
||||
private readonly KeyStoreStorageService _oldKeyStorageService;
|
||||
|
||||
public KeyStoreBackedStorageService(ISettings settings)
|
||||
{
|
||||
_oldKeyStorageService = new KeyStoreStorageService(new char[] { });
|
||||
_settings = settings;
|
||||
|
||||
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
|
||||
_keyStore.Load(null);
|
||||
|
||||
GenerateKeys();
|
||||
GenerateAesKey();
|
||||
}
|
||||
|
||||
public bool Contains(string key)
|
||||
{
|
||||
return _settings.Contains(string.Format(SettingsFormat, key)) || _oldKeyStorageService.Contains(key);
|
||||
}
|
||||
|
||||
public void Delete(string key)
|
||||
{
|
||||
CleanupOldKeyStore(key);
|
||||
_settings.Remove(string.Format(SettingsFormat, key));
|
||||
}
|
||||
|
||||
public byte[] Retrieve(string key)
|
||||
{
|
||||
var formattedKey = string.Format(SettingsFormat, key);
|
||||
if(!_settings.Contains(formattedKey))
|
||||
{
|
||||
return TryGetAndMigrateFromOldKeyStore(key);
|
||||
}
|
||||
|
||||
var cipherString = _settings.GetValueOrDefault<string>(formattedKey);
|
||||
return AesDecrypt(cipherString);
|
||||
}
|
||||
|
||||
public void Store(string key, byte[] dataBytes)
|
||||
{
|
||||
var formattedKey = string.Format(SettingsFormat, key);
|
||||
CleanupOldKeyStore(key);
|
||||
if(dataBytes == null)
|
||||
{
|
||||
_settings.Remove(formattedKey);
|
||||
return;
|
||||
}
|
||||
|
||||
var cipherString = AesEncrypt(dataBytes);
|
||||
_settings.AddOrUpdateValue(formattedKey, cipherString);
|
||||
}
|
||||
|
||||
private byte[] RandomBytes(int length)
|
||||
{
|
||||
var key = new byte[length];
|
||||
var secureRandom = new SecureRandom();
|
||||
secureRandom.NextBytes(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
private void GenerateKeys()
|
||||
{
|
||||
if(_keyStore.ContainsAlias(KeyAlias))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(_oldAndroid)
|
||||
{
|
||||
var start = Calendar.Instance;
|
||||
var end = Calendar.Instance;
|
||||
end.Add(CalendarField.Year, 30);
|
||||
|
||||
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
|
||||
var spec = new KeyPairGeneratorSpec.Builder(Application.Context)
|
||||
.SetAlias(KeyAlias)
|
||||
.SetSubject(new X500Principal($"CN={KeyAlias}"))
|
||||
.SetSerialNumber(BigInteger.Ten)
|
||||
.SetStartDate(start.Time)
|
||||
.SetEndDate(end.Time)
|
||||
.Build();
|
||||
|
||||
gen.Initialize(spec);
|
||||
gen.GenerateKeyPair();
|
||||
}
|
||||
else
|
||||
{
|
||||
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
|
||||
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
||||
.SetBlockModes(KeyProperties.BlockModeGcm)
|
||||
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
|
||||
.Build();
|
||||
|
||||
gen.Init(spec);
|
||||
gen.GenerateKey();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateAesKey()
|
||||
{
|
||||
if(!_oldAndroid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(_settings.Contains(AesKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var key = RandomBytes(16);
|
||||
var encKey = RsaEncrypt(key);
|
||||
_settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey));
|
||||
}
|
||||
|
||||
private IKey GetAesKey()
|
||||
{
|
||||
if(_oldAndroid)
|
||||
{
|
||||
var encKey = _settings.GetValueOrDefault<string>(AesKey);
|
||||
var encKeyBytes = Convert.FromBase64String(encKey);
|
||||
var key = RsaDecrypt(encKeyBytes);
|
||||
return new SecretKeySpec(key, "AES");
|
||||
}
|
||||
else
|
||||
{
|
||||
return _keyStore.GetKey(KeyAlias, null);
|
||||
}
|
||||
}
|
||||
|
||||
private KeyStore.PrivateKeyEntry GetRsaKeyEntry()
|
||||
{
|
||||
return _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry;
|
||||
}
|
||||
|
||||
private string AesEncrypt(byte[] input)
|
||||
{
|
||||
var cipher = Cipher.GetInstance(AesMode);
|
||||
cipher.Init(CipherMode.EncryptMode, GetAesKey());
|
||||
var encBytes = cipher.DoFinal(input);
|
||||
var ivBytes = cipher.GetIV();
|
||||
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
|
||||
}
|
||||
|
||||
private byte[] AesDecrypt(string cipherString)
|
||||
{
|
||||
var parts = cipherString.Split('|');
|
||||
var ivBytes = Convert.FromBase64String(parts[0]);
|
||||
var encBytes = Convert.FromBase64String(parts[1]);
|
||||
|
||||
var cipher = Cipher.GetInstance(AesMode);
|
||||
var spec = new GCMParameterSpec(128, ivBytes);
|
||||
cipher.Init(CipherMode.DecryptMode, GetAesKey(), spec);
|
||||
var decBytes = cipher.DoFinal(encBytes);
|
||||
return decBytes;
|
||||
}
|
||||
|
||||
private byte[] RsaEncrypt(byte[] input)
|
||||
{
|
||||
var entry = GetRsaKeyEntry();
|
||||
var inputCipher = Cipher.GetInstance(RsaMode, AndroidOpenSSL);
|
||||
inputCipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
|
||||
|
||||
var outputStream = new MemoryStream();
|
||||
var cipherStream = new CipherOutputStream(outputStream, inputCipher);
|
||||
cipherStream.Write(input);
|
||||
cipherStream.Close();
|
||||
|
||||
var vals = outputStream.ToArray();
|
||||
outputStream.Close();
|
||||
return vals;
|
||||
}
|
||||
|
||||
private byte[] RsaDecrypt(byte[] encInput)
|
||||
{
|
||||
var entry = GetRsaKeyEntry();
|
||||
var outputCipher = Cipher.GetInstance(RsaMode, AndroidOpenSSL);
|
||||
outputCipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
|
||||
|
||||
var inputStream = new MemoryStream(encInput);
|
||||
var cipherStream = new CipherInputStream(inputStream, outputCipher);
|
||||
|
||||
var values = new List<byte>();
|
||||
int nextByte;
|
||||
while((nextByte = cipherStream.Read()) != -1)
|
||||
{
|
||||
values.Add((byte)nextByte);
|
||||
}
|
||||
|
||||
inputStream.Close();
|
||||
cipherStream.Close();
|
||||
|
||||
var bytes = new byte[values.Count];
|
||||
for(var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = values[i];
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private byte[] TryGetAndMigrateFromOldKeyStore(string key)
|
||||
{
|
||||
if(_oldKeyStorageService.Contains(key))
|
||||
{
|
||||
var value = _oldKeyStorageService.Retrieve(key);
|
||||
Store(key, value);
|
||||
_oldKeyStorageService.Delete(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CleanupOldKeyStore(string key)
|
||||
{
|
||||
if(_oldKeyStorageService.Contains(key))
|
||||
{
|
||||
_oldKeyStorageService.Delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
[System.Obsolete]
|
||||
public class KeyStoreStorageService : ISecureStorageService
|
||||
{
|
||||
private const string StorageFile = "Bit.Android.KeyStoreStorageService";
|
||||
|
||||
129
src/Android/Utilities.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Java.Security;
|
||||
using System.IO;
|
||||
using Android.Nfc;
|
||||
using Android.Provider;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
public static class Utilities
|
||||
{
|
||||
public static bool NfcEnabled()
|
||||
{
|
||||
var manager = (NfcManager)Application.Context.GetSystemService("nfc");
|
||||
var adapter = manager.DefaultAdapter;
|
||||
return adapter != null && adapter.IsEnabled;
|
||||
}
|
||||
|
||||
public static void SendCrashEmail(Exception e, bool includeSecurityProviders = true)
|
||||
{
|
||||
SendCrashEmail(e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);
|
||||
}
|
||||
|
||||
public static void SendCrashEmail(Activity act, Exception e, bool includeSecurityProviders = true)
|
||||
{
|
||||
SendCrashEmail(act, e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);
|
||||
}
|
||||
|
||||
public static void SaveCrashFile(Exception e, bool includeSecurityProviders = true)
|
||||
{
|
||||
SaveCrashFile(e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);
|
||||
}
|
||||
|
||||
public static void SendCrashEmail(string text, bool includeSecurityProviders = true)
|
||||
{
|
||||
var emailIntent = new Intent(Intent.ActionSend);
|
||||
|
||||
emailIntent.SetType("plain/text");
|
||||
emailIntent.PutExtra(Intent.ExtraEmail, new String[] { "hello@bitwarden.com" });
|
||||
emailIntent.PutExtra(Intent.ExtraSubject, "bitwarden Crash Report");
|
||||
emailIntent.PutExtra(Intent.ExtraText, FormatText(text, includeSecurityProviders));
|
||||
|
||||
Application.Context.StartActivity(Intent.CreateChooser(emailIntent, "Send mail..."));
|
||||
}
|
||||
|
||||
public static void SendCrashEmail(Activity act, string text, bool includeSecurityProviders = true)
|
||||
{
|
||||
var emailIntent = new Intent(Intent.ActionSend);
|
||||
|
||||
emailIntent.SetType("plain/text");
|
||||
emailIntent.PutExtra(Intent.ExtraEmail, new String[] { "hello@bitwarden.com" });
|
||||
emailIntent.PutExtra(Intent.ExtraSubject, "bitwarden Crash Report");
|
||||
emailIntent.PutExtra(Intent.ExtraText, FormatText(text, includeSecurityProviders));
|
||||
|
||||
act.StartActivity(Intent.CreateChooser(emailIntent, "Send mail..."));
|
||||
}
|
||||
|
||||
public static void SaveCrashFile(string text, bool includeSecurityProviders = true)
|
||||
{
|
||||
var path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
|
||||
var filename = Path.Combine(path, $"crash-{Java.Lang.JavaSystem.CurrentTimeMillis()}.txt");
|
||||
using(var streamWriter = new StreamWriter(filename, true))
|
||||
{
|
||||
streamWriter.WriteLine(FormatText(text, includeSecurityProviders));
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatText(string text, bool includeSecurityProviders = true)
|
||||
{
|
||||
var crashMessage = "bitwarden has crashed. Please send this email to our support team so that we can help " +
|
||||
"resolve the problem for you. Thank you.";
|
||||
|
||||
text = crashMessage + "\n\n =============================================== \n\n" + text;
|
||||
|
||||
if(includeSecurityProviders)
|
||||
{
|
||||
text += "\n\n";
|
||||
var providers = Security.GetProviders();
|
||||
foreach(var provider in providers)
|
||||
{
|
||||
text += ("provider: " + provider.Name + "\n");
|
||||
var services = provider.Services;
|
||||
foreach(var service in provider.Services)
|
||||
{
|
||||
text += ("- alg: " + service.Algorithm + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text += "\n\n ==================================================== \n\n" + crashMessage;
|
||||
return text;
|
||||
}
|
||||
|
||||
public static string AppendExceptionToMessage(string message, Exception ex)
|
||||
{
|
||||
message += ("\n\n" + ex.Message + "\n\n" + ex.StackTrace);
|
||||
if(ex.InnerException != null)
|
||||
{
|
||||
return AppendExceptionToMessage(message, ex.InnerException);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static string GetFileName(Context context, global::Android.Net.Uri uri)
|
||||
{
|
||||
string name = null;
|
||||
string[] projection = { MediaStore.MediaColumns.DisplayName };
|
||||
var metaCursor = context.ContentResolver.Query(uri, projection, null, null, null);
|
||||
if(metaCursor != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(metaCursor.MoveToFirst())
|
||||
{
|
||||
name = metaCursor.GetString(0);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
metaCursor.Close();
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,11 @@
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.7.0" newVersion="4.0.7.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.8.0" newVersion="4.0.8.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="PInvoke.BCrypt" publicKeyToken="9e300f9f87f04a7a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-0.5.0.0" newVersion="0.5.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
||||
@@ -5,28 +5,25 @@
|
||||
<package id="AndHUD" version="1.2.0" targetFramework="monoandroid60" />
|
||||
<package id="BouncyCastle" version="1.8.1" targetFramework="monoandroid60" />
|
||||
<package id="CommonServiceLocator" version="1.3" targetFramework="monoandroid60" />
|
||||
<package id="HockeySDK.Xamarin" version="4.1.2" targetFramework="monoandroid71" />
|
||||
<package id="HockeySDK.Xamarin" version="4.1.5" targetFramework="monoandroid71" />
|
||||
<package id="Microsoft.NETCore.Platforms" version="1.0.1" targetFramework="monoandroid71" />
|
||||
<package id="Microsoft.Win32.Primitives" version="4.0.1" targetFramework="monoandroid71" />
|
||||
<package id="NETStandard.Library" version="1.6.0" targetFramework="monoandroid71" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="monoandroid60" />
|
||||
<package id="PCLCrypto" version="2.0.147" targetFramework="monoandroid60" />
|
||||
<package id="PInvoke.BCrypt" version="0.3.152" targetFramework="monoandroid60" />
|
||||
<package id="PInvoke.Kernel32" version="0.3.152" targetFramework="monoandroid60" />
|
||||
<package id="PInvoke.NCrypt" version="0.3.152" targetFramework="monoandroid60" />
|
||||
<package id="PInvoke.Windows.Core" version="0.3.152" targetFramework="monoandroid60" />
|
||||
<package id="PInvoke.BCrypt" version="0.5.97" targetFramework="monoandroid71" />
|
||||
<package id="PInvoke.Kernel32" version="0.5.97" targetFramework="monoandroid71" />
|
||||
<package id="PInvoke.NCrypt" version="0.5.97" targetFramework="monoandroid71" />
|
||||
<package id="PInvoke.Windows.Core" version="0.5.97" targetFramework="monoandroid71" />
|
||||
<package id="Plugin.CurrentActivity" version="1.0.1" targetFramework="monoandroid60" />
|
||||
<package id="Plugin.Fingerprint" version="1.4.4" targetFramework="monoandroid71" />
|
||||
<package id="SimpleInjector" version="4.0.7" targetFramework="monoandroid71" />
|
||||
<package id="Plugin.Fingerprint" version="1.4.5" targetFramework="monoandroid71" />
|
||||
<package id="SimpleInjector" version="4.0.8" targetFramework="monoandroid71" />
|
||||
<package id="Splat" version="1.6.2" targetFramework="monoandroid60" />
|
||||
<package id="sqlite-net-pcl" version="1.2.1" targetFramework="monoandroid60" />
|
||||
<package id="SQLitePCL.bundle_green" version="0.9.3" targetFramework="monoandroid60" />
|
||||
<package id="SQLitePCL.plugin.sqlite3.android" version="0.9.3" targetFramework="monoandroid60" />
|
||||
<package id="SQLitePCL.raw" version="0.9.3" targetFramework="monoandroid60" />
|
||||
<package id="SQLitePCLRaw.bundle_green" version="1.1.2" targetFramework="monoandroid60" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="monoandroid60" />
|
||||
<package id="SQLitePCLRaw.lib.e_sqlite3.android" version="1.1.2" targetFramework="monoandroid60" />
|
||||
<package id="SQLitePCLRaw.provider.e_sqlite3.android" version="1.1.2" targetFramework="monoandroid60" />
|
||||
<package id="sqlite-net-pcl" version="1.4.118" targetFramework="monoandroid71" />
|
||||
<package id="SQLitePCLRaw.bundle_green" version="1.1.8" targetFramework="monoandroid71" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="monoandroid71" />
|
||||
<package id="SQLitePCLRaw.lib.e_sqlite3.android" version="1.1.8" targetFramework="monoandroid71" />
|
||||
<package id="SQLitePCLRaw.provider.e_sqlite3.android" version="1.1.8" targetFramework="monoandroid71" />
|
||||
<package id="System.AppContext" version="4.1.0" targetFramework="monoandroid71" />
|
||||
<package id="System.Collections" version="4.0.11" targetFramework="monoandroid71" />
|
||||
<package id="System.Collections.Concurrent" version="4.0.12" targetFramework="monoandroid71" />
|
||||
@@ -71,9 +68,9 @@
|
||||
<package id="System.Xml.ReaderWriter" version="4.0.11" targetFramework="monoandroid71" />
|
||||
<package id="System.Xml.XDocument" version="4.0.11" targetFramework="monoandroid71" />
|
||||
<package id="Validation" version="2.3.7" targetFramework="monoandroid60" />
|
||||
<package id="Xam.Plugin.Connectivity" version="2.3.0" targetFramework="monoandroid71" />
|
||||
<package id="Xam.Plugin.Connectivity" version="3.0.2" targetFramework="monoandroid71" />
|
||||
<package id="Xam.Plugin.PushNotification" version="1.2.4" targetFramework="monoandroid60" developmentDependency="true" />
|
||||
<package id="Xam.Plugins.Settings" version="2.5.4" targetFramework="monoandroid71" />
|
||||
<package id="Xam.Plugins.Settings" version="3.0.1" targetFramework="monoandroid71" />
|
||||
<package id="Xamarin.Android.Support.Animated.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.Android.Support.Design" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.Android.Support.v4" version="23.3.0" targetFramework="monoandroid60" />
|
||||
@@ -84,7 +81,7 @@
|
||||
<package id="Xamarin.Android.Support.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.FFImageLoading" version="2.2.9" targetFramework="monoandroid71" />
|
||||
<package id="Xamarin.FFImageLoading.Forms" version="2.2.9" targetFramework="monoandroid71" />
|
||||
<package id="Xamarin.Forms" version="2.3.4.231" targetFramework="monoandroid71" />
|
||||
<package id="Xamarin.Forms" version="2.3.4.267" targetFramework="monoandroid71" />
|
||||
<package id="Xamarin.GooglePlayServices.Analytics" version="29.0.0.2" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.GooglePlayServices.Base" version="29.0.0.2" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.GooglePlayServices.Basement" version="29.0.0.2" targetFramework="monoandroid60" />
|
||||
@@ -92,4 +89,6 @@
|
||||
<package id="Xamarin.GooglePlayServices.Measurement" version="29.0.0.2" targetFramework="monoandroid60" />
|
||||
<package id="XLabs.IoC" version="2.0.5782" targetFramework="monoandroid60" />
|
||||
<package id="XLabs.IoC.SimpleInjector" version="2.0.5782" targetFramework="monoandroid71" />
|
||||
<package id="ZXing.Net.Mobile" version="2.1.47" targetFramework="monoandroid71" />
|
||||
<package id="ZXing.Net.Mobile.Forms" version="2.1.47" targetFramework="monoandroid71" />
|
||||
</packages>
|
||||
13
src/App/Abstractions/Repositories/IAttachmentRepository.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models.Data;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IAttachmentRepository : IRepository<AttachmentData, string>
|
||||
{
|
||||
Task<IEnumerable<AttachmentData>> GetAllByLoginIdAsync(string loginId);
|
||||
Task<IEnumerable<AttachmentData>> GetAllByUserIdAsync(string userId);
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,7 @@ namespace Bit.App.Abstractions
|
||||
{
|
||||
Task<ApiResult<CipherResponse>> GetByIdAsync(string id);
|
||||
Task<ApiResult<ListResponse<CipherResponse>>> GetAsync();
|
||||
Task<ApiResult<CipherResponse>> PostAttachmentAsync(string cipherId, byte[] data, string fileName);
|
||||
Task<ApiResult> DeleteAttachmentAsync(string cipherId, string attachmentId);
|
||||
}
|
||||
}
|
||||
10
src/App/Abstractions/Repositories/ITwoFactorApiRepository.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models.Api;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface ITwoFactorApiRepository
|
||||
{
|
||||
Task<ApiResult> PostSendEmailLoginAsync(TwoFactorEmailRequest requestObj);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,13 @@ namespace Bit.App.Abstractions
|
||||
{
|
||||
bool Locked { get; set; }
|
||||
DateTime LastActivity { get; set; }
|
||||
DateTime LastCacheClear { get; set; }
|
||||
bool AutofillPersistNotification { get; set; }
|
||||
bool AutofillPasswordField { get; set; }
|
||||
string SecurityStamp { get; set; }
|
||||
string BaseUrl { get; set; }
|
||||
string WebVaultUrl { get; set; }
|
||||
string ApiUrl { get; set; }
|
||||
string IdentityUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Enums;
|
||||
using Bit.App.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
@@ -15,6 +16,7 @@ namespace Bit.App.Abstractions
|
||||
bool BelongsToOrganization(string orgId);
|
||||
void LogOut();
|
||||
Task<FullLoginResult> TokenPostAsync(string email, string masterPassword);
|
||||
Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, SymmetricCryptoKey key);
|
||||
Task<LoginResult> TokenPostTwoFactorAsync(TwoFactorProviderType type, string token, bool remember, string email,
|
||||
string masterPasswordHash, SymmetricCryptoKey key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IClipboardService
|
||||
{
|
||||
void CopyToClipboard(string text);
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,10 @@ namespace Bit.App.Abstractions
|
||||
void ClearKeys();
|
||||
string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null);
|
||||
byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null);
|
||||
byte[] DecryptToBytes(byte[] encyptedValue, SymmetricCryptoKey key = null);
|
||||
byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey);
|
||||
CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null);
|
||||
byte[] EncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key = null);
|
||||
SymmetricCryptoKey MakeKeyFromPassword(string password, string salt);
|
||||
string MakeKeyFromPasswordBase64(string password, string salt);
|
||||
byte[] HashPassword(SymmetricCryptoKey key, string password);
|
||||
|
||||
14
src/App/Abstractions/Services/IDeviceActionService.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IDeviceActionService
|
||||
{
|
||||
void CopyToClipboard(string text);
|
||||
bool OpenFile(byte[] fileData, string id, string fileName);
|
||||
bool CanOpenFile(string fileName);
|
||||
Task SelectFileAsync();
|
||||
void ClearCache();
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,6 @@
|
||||
string Model { get; }
|
||||
int Version { get; }
|
||||
float Scale { get; }
|
||||
bool NfcEnabled { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,8 @@ namespace Bit.App.Abstractions
|
||||
Task<Tuple<IEnumerable<Login>, IEnumerable<Login>>> GetAllAsync(string uriString);
|
||||
Task<ApiResult<LoginResponse>> SaveAsync(Login login);
|
||||
Task<ApiResult> DeleteAsync(string id);
|
||||
Task<byte[]> DownloadAndDecryptAttachmentAsync(string url, string orgId = null);
|
||||
Task<ApiResult<CipherResponse>> EncryptAndSaveAttachmentAsync(Login login, byte[] data, string fileName);
|
||||
Task<ApiResult> DeleteAttachmentAsync(Login login, string attachmentId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace Bit.App.Abstractions
|
||||
{
|
||||
string Token { get; set; }
|
||||
string RefreshToken { get; set; }
|
||||
[Obsolete("Old auth scheme")]
|
||||
string AuthBearer { get; set; }
|
||||
string GetTwoFactorToken(string email);
|
||||
void SetTwoFactorToken(string email, string token);
|
||||
DateTime TokenExpiration { get; }
|
||||
string TokenIssuer { get; }
|
||||
bool TokenExpired { get; }
|
||||
@@ -16,5 +16,6 @@ namespace Bit.App.Abstractions
|
||||
string TokenUserId { get; }
|
||||
string TokenEmail { get; }
|
||||
string TokenName { get; }
|
||||
bool TokenPremium { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@ using Acr.UserDialogs;
|
||||
using XLabs.Ioc;
|
||||
using System.Reflection;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App
|
||||
{
|
||||
public class App : Application
|
||||
{
|
||||
private const string LastBuildKey = "LastBuild";
|
||||
|
||||
private string _uri;
|
||||
private readonly IDatabaseService _databaseService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
@@ -32,6 +31,7 @@ namespace Bit.App
|
||||
private readonly ILocalizeService _localizeService;
|
||||
private readonly IAppInfoService _appInfoService;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
|
||||
public App(
|
||||
string uri,
|
||||
@@ -45,7 +45,8 @@ namespace Bit.App
|
||||
IGoogleAnalyticsService googleAnalyticsService,
|
||||
ILocalizeService localizeService,
|
||||
IAppInfoService appInfoService,
|
||||
IAppSettingsService appSettingsService)
|
||||
IAppSettingsService appSettingsService,
|
||||
IDeviceActionService deviceActionService)
|
||||
{
|
||||
_uri = uri;
|
||||
_databaseService = databaseService;
|
||||
@@ -59,6 +60,7 @@ namespace Bit.App
|
||||
_localizeService = localizeService;
|
||||
_appInfoService = appInfoService;
|
||||
_appSettingsService = appSettingsService;
|
||||
_deviceActionService = deviceActionService;
|
||||
|
||||
SetCulture();
|
||||
SetStyles();
|
||||
@@ -100,16 +102,15 @@ namespace Bit.App
|
||||
|
||||
if(string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
var lastBuild = _settings.GetValueOrDefault<string>(LastBuildKey);
|
||||
if(InDebugMode() || lastBuild == null || lastBuild != _appInfoService.Build)
|
||||
{
|
||||
_settings.AddOrUpdateValue(LastBuildKey, _appInfoService.Build);
|
||||
_databaseService.CreateTables();
|
||||
}
|
||||
|
||||
Helpers.PerformUpdateTasks(_settings, _appInfoService, _databaseService);
|
||||
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if((DateTime.UtcNow - _appSettingsService.LastCacheClear).TotalDays >= 1)
|
||||
{
|
||||
await Task.Run(() => _deviceActionService.ClearCache()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Debug.WriteLine("OnStart");
|
||||
}
|
||||
|
||||
@@ -152,6 +153,13 @@ namespace Bit.App
|
||||
{
|
||||
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
if((now - _appSettingsService.LastCacheClear).TotalDays >= 1
|
||||
&& (now - _appSettingsService.LastActivity).TotalHours >= 1)
|
||||
{
|
||||
await Task.Run(() => _deviceActionService.ClearCache()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMainPageFromAutofill()
|
||||
@@ -169,15 +177,6 @@ namespace Bit.App
|
||||
}
|
||||
}
|
||||
|
||||
private bool InDebugMode()
|
||||
{
|
||||
#if DEBUG
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
private async Task FullSyncAsync()
|
||||
{
|
||||
if(_connectivity.IsConnected)
|
||||
@@ -216,13 +215,13 @@ namespace Bit.App
|
||||
}
|
||||
}
|
||||
|
||||
private async void Logout(string logoutMessage)
|
||||
private void Logout(string logoutMessage)
|
||||
{
|
||||
_authService.LogOut();
|
||||
|
||||
var deviceApiRepository = Resolver.Resolve<IDeviceApiRepository>();
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
await Task.Run(() => deviceApiRepository.PutClearTokenAsync(appIdService.AppId)).ConfigureAwait(false);
|
||||
Task.Run(async () => await deviceApiRepository.PutClearTokenAsync(appIdService.AppId));
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedOut");
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Abstractions\Repositories\IAttachmentRepository.cs" />
|
||||
<Compile Include="Abstractions\Repositories\ITwoFactorApiRepository.cs" />
|
||||
<Compile Include="Abstractions\Repositories\ISettingsApiRepository.cs" />
|
||||
<Compile Include="Abstractions\Repositories\IAccountsApiRepository.cs" />
|
||||
<Compile Include="Abstractions\Repositories\IDeviceApiRepository.cs" />
|
||||
@@ -49,7 +51,7 @@
|
||||
<Compile Include="Abstractions\Services\IAppInfoService.cs" />
|
||||
<Compile Include="Abstractions\Services\IAppIdService.cs" />
|
||||
<Compile Include="Abstractions\Services\IAuthService.cs" />
|
||||
<Compile Include="Abstractions\Services\IClipboardService.cs" />
|
||||
<Compile Include="Abstractions\Services\IDeviceActionService.cs" />
|
||||
<Compile Include="Abstractions\Services\IKeyDerivationService.cs" />
|
||||
<Compile Include="Abstractions\Services\ILogService.cs" />
|
||||
<Compile Include="Abstractions\Services\IReflectionService.cs" />
|
||||
@@ -59,12 +61,14 @@
|
||||
<Compile Include="Abstractions\Services\ISecureStorageService.cs" />
|
||||
<Compile Include="Abstractions\Services\ISqlService.cs" />
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="Controls\HybridWebView.cs" />
|
||||
<Compile Include="Controls\ExtendedToolbarItem.cs" />
|
||||
<Compile Include="Controls\DismissModalToolBarItem.cs" />
|
||||
<Compile Include="Controls\ExtendedEditor.cs" />
|
||||
<Compile Include="Controls\ExtendedButton.cs" />
|
||||
<Compile Include="Controls\ExtendedNavigationPage.cs" />
|
||||
<Compile Include="Controls\ExtendedContentPage.cs" />
|
||||
<Compile Include="Controls\LabeledRightDetailCell.cs" />
|
||||
<Compile Include="Controls\MemoryContentView.cs" />
|
||||
<Compile Include="Controls\StepperCell.cs" />
|
||||
<Compile Include="Controls\ExtendedTableView.cs" />
|
||||
@@ -80,7 +84,9 @@
|
||||
<Compile Include="Controls\FormPickerCell.cs" />
|
||||
<Compile Include="Controls\FormEntryCell.cs" />
|
||||
<Compile Include="Controls\PinControl.cs" />
|
||||
<Compile Include="Controls\VaultAttachmentsViewCell.cs" />
|
||||
<Compile Include="Controls\VaultListViewCell.cs" />
|
||||
<Compile Include="Enums\TwoFactorProviderType.cs" />
|
||||
<Compile Include="Enums\EncryptionType.cs" />
|
||||
<Compile Include="Enums\OrganizationUserType.cs" />
|
||||
<Compile Include="Enums\LockType.cs" />
|
||||
@@ -95,11 +101,12 @@
|
||||
<Compile Include="Models\Api\Request\DeviceTokenRequest.cs" />
|
||||
<Compile Include="Models\Api\Request\FolderRequest.cs" />
|
||||
<Compile Include="Models\Api\Request\DeviceRequest.cs" />
|
||||
<Compile Include="Models\Api\Request\TwoFactorEmailRequest.cs" />
|
||||
<Compile Include="Models\Api\Request\RegisterRequest.cs" />
|
||||
<Compile Include="Models\Api\Request\LoginRequest.cs" />
|
||||
<Compile Include="Models\Api\Request\PasswordHintRequest.cs" />
|
||||
<Compile Include="Models\Api\Request\TokenRequest.cs" />
|
||||
<Compile Include="Models\Api\Response\CipherHistoryResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\AttachmentResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\CipherResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\DomainsResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\ErrorResponse.cs" />
|
||||
@@ -112,8 +119,10 @@
|
||||
<Compile Include="Models\Api\Response\TokenResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\ProfileResponse.cs" />
|
||||
<Compile Include="Models\Api\LoginDataModel.cs" />
|
||||
<Compile Include="Models\Cipher.cs" />
|
||||
<Compile Include="Models\CipherString.cs" />
|
||||
<Compile Include="Models\Data\AttachmentData.cs" />
|
||||
<Compile Include="Models\Attachment.cs" />
|
||||
<Compile Include="Models\Page\VaultAttachmentsPageModel.cs" />
|
||||
<Compile Include="Models\SymmetricCryptoKey.cs" />
|
||||
<Compile Include="Models\Data\SettingsData.cs" />
|
||||
<Compile Include="Models\Data\FolderData.cs" />
|
||||
@@ -135,7 +144,9 @@
|
||||
<Compile Include="Pages\Lock\LockPasswordPage.cs" />
|
||||
<Compile Include="Pages\LoginTwoFactorPage.cs" />
|
||||
<Compile Include="Pages\PasswordHintPage.cs" />
|
||||
<Compile Include="Pages\EnvironmentPage.cs" />
|
||||
<Compile Include="Pages\RegisterPage.cs" />
|
||||
<Compile Include="Pages\ScanPage.cs" />
|
||||
<Compile Include="Pages\Settings\SettingsCreditsPage.cs" />
|
||||
<Compile Include="Pages\Settings\SettingsHelpPage.cs" />
|
||||
<Compile Include="Pages\Settings\SettingsFeaturesPage.cs" />
|
||||
@@ -154,8 +165,11 @@
|
||||
<Compile Include="Pages\Settings\SettingsPage.cs" />
|
||||
<Compile Include="Pages\Settings\SettingsListFoldersPage.cs" />
|
||||
<Compile Include="Pages\Vault\VaultAutofillListLoginsPage.cs" />
|
||||
<Compile Include="Pages\Vault\VaultAttachmentsPage.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Abstractions\Repositories\ILoginRepository.cs" />
|
||||
<Compile Include="Repositories\AttachmentRepository.cs" />
|
||||
<Compile Include="Repositories\TwoFactorApiRepository.cs" />
|
||||
<Compile Include="Repositories\SettingsApiRepository.cs" />
|
||||
<Compile Include="Repositories\ApiRepository.cs" />
|
||||
<Compile Include="Repositories\AccountsApiRepository.cs" />
|
||||
@@ -205,6 +219,11 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AppResources.hi.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Resources\AppResources.hr.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AppResources.hr.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Resources\AppResources.id.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
@@ -220,6 +239,11 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AppResources.ja.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Resources\AppResources.pl.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AppResources.pl.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Resources\AppResources.pt-BR.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
@@ -230,6 +254,11 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AppResources.pt-PT.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Resources\AppResources.ro.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AppResources.ro.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Resources\AppResources.ru.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
@@ -285,6 +314,8 @@
|
||||
<Compile Include="Pages\Vault\VaultEditLoginPage.cs" />
|
||||
<Compile Include="Pages\Vault\VaultListLoginsPage.cs" />
|
||||
<Compile Include="Services\PasswordGenerationService.cs" />
|
||||
<Compile Include="Utilities\Base32.cs" />
|
||||
<Compile Include="Utilities\Crypto.cs" />
|
||||
<Compile Include="Utilities\Helpers.cs" />
|
||||
<Compile Include="Utilities\IdentityHttpClient.cs" />
|
||||
<Compile Include="Utilities\Extentions.cs" />
|
||||
@@ -317,6 +348,10 @@
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.hi.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\AppResources.hr.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.hr.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\AppResources.id.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.id.Designer.cs</LastGenOutput>
|
||||
@@ -329,6 +364,10 @@
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.ja.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\AppResources.pl.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.pl.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\AppResources.pt-BR.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.pt-BR.Designer.cs</LastGenOutput>
|
||||
@@ -337,6 +376,10 @@
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.pt-PT.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\AppResources.ro.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.ro.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\AppResources.ru.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.ru.Designer.cs</LastGenOutput>
|
||||
@@ -379,8 +422,8 @@
|
||||
<Reference Include="FFImageLoading.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.9\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Platform.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="HockeySDK, Version=1.0.6288.33979, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\HockeySDK.dll</HintPath>
|
||||
<Reference Include="HockeySDK, Version=1.0.6387.33440, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.5\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\HockeySDK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
|
||||
@@ -397,39 +440,35 @@
|
||||
<HintPath>..\..\packages\PCLCrypto.2.0.147\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\PCLCrypto.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="PInvoke.BCrypt, Version=0.3.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.BCrypt.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.BCrypt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="PInvoke.BCrypt, Version=0.5.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.BCrypt.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.BCrypt.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PInvoke.Kernel32, Version=0.3.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.Kernel32.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Kernel32.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="PInvoke.Kernel32, Version=0.5.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.Kernel32.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.Kernel32.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PInvoke.NCrypt, Version=0.3.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.NCrypt.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.NCrypt.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="PInvoke.NCrypt, Version=0.5.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.NCrypt.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.NCrypt.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PInvoke.Windows.Core, Version=0.3.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.Windows.Core.0.3.152\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\PInvoke.Windows.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="PInvoke.Windows.Core, Version=0.5.0.0, Culture=neutral, PublicKeyToken=9e300f9f87f04a7a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\PInvoke.Windows.Core.0.5.97\lib\portable-net45+win8+wpa81\PInvoke.Windows.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Connectivity, Version=2.3.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Connectivity.dll</HintPath>
|
||||
<Reference Include="Plugin.Connectivity, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.3.0.2\lib\netstandard1.0\Plugin.Connectivity.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Connectivity.Abstractions, Version=2.3.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.3.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Connectivity.Abstractions.dll</HintPath>
|
||||
<Reference Include="Plugin.Connectivity.Abstractions, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.3.0.2\lib\netstandard1.0\Plugin.Connectivity.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Fingerprint, Version=1.4.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.4\lib\portable-net45+win8+wpa81+wp8\Plugin.Fingerprint.dll</HintPath>
|
||||
<Reference Include="Plugin.Fingerprint, Version=1.4.5.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.5\lib\portable-net45+win8+wpa81+wp8\Plugin.Fingerprint.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Fingerprint.Abstractions, Version=1.4.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.4\lib\portable-net45+win8+wpa81+wp8\Plugin.Fingerprint.Abstractions.dll</HintPath>
|
||||
<Reference Include="Plugin.Fingerprint.Abstractions, Version=1.4.5.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.4.5\lib\portable-net45+win8+wpa81+wp8\Plugin.Fingerprint.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Settings, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.dll</HintPath>
|
||||
<Reference Include="Plugin.Settings, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.3.0.1\lib\netstandard1.0\Plugin.Settings.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.4\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.Abstractions.dll</HintPath>
|
||||
<Reference Include="Plugin.Settings.Abstractions, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.3.0.1\lib\netstandard1.0\Plugin.Settings.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PushNotification.Plugin, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\PushNotification.Plugin.dll</HintPath>
|
||||
@@ -443,47 +482,47 @@
|
||||
<HintPath>..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLite-net, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\sqlite-net-pcl.1.2.1\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLite-net.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCL.batteries, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCL.bundle_green.0.9.3\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCL.batteries.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCL.raw, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCL.raw.0.9.3\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCL.raw.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="SQLite-net, Version=1.4.118.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\sqlite-net-pcl.1.4.118\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLite-net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.batteries_green, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a84b7dcfb1391f7f, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.batteries_v2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8226ea5df37bcae9, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.core.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\SQLitePCLRaw.core.1.1.8\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Validation, Version=2.3.0.0, Culture=neutral, PublicKeyToken=2fc06f0d701809a7, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Validation.2.3.7\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Validation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.267\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.267\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Xaml, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.267\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="XLabs.Ioc, Version=2.0.5782.12218, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\XLabs.IoC.2.0.5782\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1+Xamarin.iOS10\XLabs.Ioc.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ZXing.Net.Mobile.Core, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXing.Net.Mobile.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ZXing.Net.Mobile.Forms, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXing.Net.Mobile.Forms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="zxing.portable, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\zxing.portable.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ZXingNetMobile, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXingNetMobile.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\AppResources.resx">
|
||||
@@ -495,12 +534,12 @@
|
||||
<Compile Include="Services\PushNotificationListener.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
<Import Project="..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
|
||||
<Import Project="..\..\packages\Xamarin.Forms.2.3.4.267\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.4.267\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.4.267\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.4.267\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
public const string SettingPinUnlockOn = "setting:pinUnlockOn";
|
||||
public const string SettingLockSeconds = "setting:lockSeconds";
|
||||
public const string SettingGaOptOut = "setting:googleAnalyticsOptOut";
|
||||
public const string SettingDisableTotpCopy = "setting:disableAutoCopyTotp";
|
||||
public const string AutofillPersistNotification = "setting:persistNotification";
|
||||
public const string AutofillPasswordField = "setting:autofillPasswordField";
|
||||
|
||||
@@ -28,8 +29,17 @@
|
||||
|
||||
public const string SecurityStamp = "other:securityStamp";
|
||||
public const string LastActivityDate = "other:lastActivityDate";
|
||||
public const string LastCacheClearDate = "other:cacheClearDate";
|
||||
public const string Locked = "other:locked";
|
||||
public const string LastLoginEmail = "other:lastLoginEmail";
|
||||
public const string LastSync = "other:lastSync";
|
||||
public const string LastBuildKey = "LastBuild";
|
||||
public const string BaseUrl = "other:baseUrl";
|
||||
public const string WebVaultUrl = "other:webVaultUrl";
|
||||
public const string ApiUrl = "other:apiUrl";
|
||||
public const string IdentityUrl = "other:identityUrl";
|
||||
|
||||
public const int SelectFileRequestCode = 42;
|
||||
public const int SelectFilePermissionRequestCode = 43;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace Bit.App.Controls
|
||||
}
|
||||
|
||||
base.OnDisappearing();
|
||||
MessagingCenter.Send(Application.Current, "DismissKeyboard");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
src/App/Controls/HybridWebView.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class HybridWebView : View
|
||||
{
|
||||
private Action<string> _func;
|
||||
|
||||
public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: nameof(Uri),
|
||||
returnType: typeof(string), declaringType: typeof(HybridWebView), defaultValue: default(string));
|
||||
|
||||
public string Uri
|
||||
{
|
||||
get { return (string)GetValue(UriProperty); }
|
||||
set { SetValue(UriProperty, value); }
|
||||
}
|
||||
|
||||
public void RegisterAction(Action<string> callback)
|
||||
{
|
||||
_func = callback;
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_func = null;
|
||||
}
|
||||
|
||||
public void InvokeAction(string data)
|
||||
{
|
||||
_func?.Invoke(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,14 @@ namespace Bit.App.Controls
|
||||
Margin = new Thickness(5, 0, 0, 0)
|
||||
};
|
||||
|
||||
LabelIcon2 = new CachedImage
|
||||
{
|
||||
WidthRequest = 16,
|
||||
HeightRequest = 16,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
Margin = new Thickness(5, 0, 0, 0)
|
||||
};
|
||||
|
||||
Button = new ExtendedButton
|
||||
{
|
||||
WidthRequest = 60
|
||||
@@ -42,13 +50,15 @@ namespace Bit.App.Controls
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(60, GridUnitType.Absolute) });
|
||||
grid.Children.Add(Label, 0, 0);
|
||||
grid.Children.Add(Detail, 0, 1);
|
||||
grid.Children.Add(LabelIcon, 1, 0);
|
||||
grid.Children.Add(Button, 2, 0);
|
||||
Grid.SetColumnSpan(Detail, 2);
|
||||
grid.Children.Add(LabelIcon2, 2, 0);
|
||||
grid.Children.Add(Button, 3, 0);
|
||||
Grid.SetColumnSpan(Detail, 3);
|
||||
Grid.SetRowSpan(Button, 2);
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
@@ -62,6 +72,7 @@ namespace Bit.App.Controls
|
||||
public Label Label { get; private set; }
|
||||
public Label Detail { get; private set; }
|
||||
public CachedImage LabelIcon { get; private set; }
|
||||
public CachedImage LabelIcon2 { get; private set; }
|
||||
public Button Button { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
59
src/App/Controls/LabeledRightDetailCell.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using FFImageLoading.Forms;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class LabeledRightDetailCell : ExtendedViewCell
|
||||
{
|
||||
public LabeledRightDetailCell(bool showIcon = true)
|
||||
{
|
||||
Label = new Label
|
||||
{
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
HorizontalOptions = LayoutOptions.StartAndExpand,
|
||||
};
|
||||
|
||||
Detail = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
HorizontalOptions = LayoutOptions.End,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
};
|
||||
|
||||
StackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
Padding = new Thickness(15, 10),
|
||||
Children = { Label, Detail }
|
||||
};
|
||||
|
||||
if(showIcon)
|
||||
{
|
||||
Icon = new CachedImage
|
||||
{
|
||||
WidthRequest = 16,
|
||||
HeightRequest = 16,
|
||||
HorizontalOptions = LayoutOptions.End,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(5, 0, 0, 0)
|
||||
};
|
||||
|
||||
StackLayout.Children.Add(Icon);
|
||||
}
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Label.TextColor = Color.Black;
|
||||
}
|
||||
|
||||
View = StackLayout;
|
||||
}
|
||||
|
||||
public Label Label { get; private set; }
|
||||
public Label Detail { get; private set; }
|
||||
public CachedImage Icon { get; private set; }
|
||||
public StackLayout StackLayout { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ namespace Bit.App.Controls
|
||||
string labelText = null,
|
||||
string valueText = null,
|
||||
string button1Text = null,
|
||||
string button2Text = null)
|
||||
string button2Text = null,
|
||||
string subText = null)
|
||||
{
|
||||
var containerStackLayout = new StackLayout
|
||||
{
|
||||
@@ -56,6 +57,18 @@ namespace Bit.App.Controls
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand
|
||||
};
|
||||
|
||||
if(subText != null)
|
||||
{
|
||||
Sub = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
HorizontalOptions = LayoutOptions.End,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
};
|
||||
|
||||
buttonStackLayout.Children.Add(Sub);
|
||||
}
|
||||
|
||||
if(button1Text != null)
|
||||
{
|
||||
Button1 = new ExtendedButton
|
||||
@@ -100,12 +113,18 @@ namespace Bit.App.Controls
|
||||
containerStackLayout.AdjustPaddingForDevice();
|
||||
}
|
||||
|
||||
if(Sub != null && Button1 != null)
|
||||
{
|
||||
Sub.Margin = new Thickness(0, 0, 10, 0);
|
||||
}
|
||||
|
||||
containerStackLayout.Children.Add(buttonStackLayout);
|
||||
View = containerStackLayout;
|
||||
}
|
||||
|
||||
public Label Label { get; private set; }
|
||||
public Label Value { get; private set; }
|
||||
public Label Sub { get; private set; }
|
||||
public ExtendedButton Button1 { get; private set; }
|
||||
public ExtendedButton Button2 { get; private set; }
|
||||
}
|
||||
|
||||
22
src/App/Controls/VaultAttachmentsViewCell.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Bit.App.Models.Page;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class VaultAttachmentsViewCell : LabeledRightDetailCell
|
||||
{
|
||||
public VaultAttachmentsViewCell()
|
||||
{
|
||||
Label.SetBinding(Label.TextProperty, nameof(VaultAttachmentsPageModel.Attachment.Name));
|
||||
Detail.SetBinding(Label.TextProperty, nameof(VaultAttachmentsPageModel.Attachment.SizeName));
|
||||
Icon.Source = "trash";
|
||||
Detail.MinimumWidthRequest = 100;
|
||||
BackgroundColor = Color.White;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
StackLayout.BackgroundColor = Color.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,14 @@ namespace Bit.App.Controls
|
||||
Label.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Login.Name));
|
||||
Detail.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Login.Username));
|
||||
LabelIcon.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Login.Shared));
|
||||
LabelIcon2.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Login.HasAttachments));
|
||||
|
||||
Button.Image = "more";
|
||||
Button.Command = new Command(() => moreClickedAction?.Invoke(LoginParameter));
|
||||
Button.BackgroundColor = Color.Transparent;
|
||||
|
||||
LabelIcon.Source = "share";
|
||||
LabelIcon2.Source = "paperclip";
|
||||
|
||||
BackgroundColor = Color.White;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
AesCbc128_HmacSha256_B64 = 1,
|
||||
AesCbc256_HmacSha256_B64 = 2,
|
||||
Rsa2048_OaepSha256_B64 = 3,
|
||||
Rsa2048_OaepSha1_B64 = 4
|
||||
Rsa2048_OaepSha1_B64 = 4,
|
||||
Rsa2048_OaepSha256_HmacSha256_B64 = 5,
|
||||
Rsa2048_OaepSha1_HmacSha256_B64 = 6
|
||||
}
|
||||
}
|
||||
|
||||
12
src/App/Enums/TwoFactorProviderType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Bit.App.Enums
|
||||
{
|
||||
public enum TwoFactorProviderType : byte
|
||||
{
|
||||
Authenticator = 0,
|
||||
Email = 1,
|
||||
Duo = 2,
|
||||
YubiKey = 3,
|
||||
U2f = 4,
|
||||
Remember = 5
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,6 @@
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Totp { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
Username = login.Username?.EncryptedString;
|
||||
Password = login.Password?.EncryptedString;
|
||||
Notes = login.Notes?.EncryptedString;
|
||||
Totp = login.Totp?.EncryptedString;
|
||||
Favorite = login.Favorite;
|
||||
}
|
||||
|
||||
@@ -21,6 +22,7 @@
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Totp { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Bit.App.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
@@ -8,10 +9,9 @@ namespace Bit.App.Models.Api
|
||||
public string Email { get; set; }
|
||||
public string MasterPasswordHash { get; set; }
|
||||
public string Token { get; set; }
|
||||
public int? Provider { get; set; }
|
||||
[Obsolete]
|
||||
public string OldAuthBearer { get; set; }
|
||||
public TwoFactorProviderType? Provider { get; set; }
|
||||
public DeviceRequest Device { get; set; }
|
||||
public bool Remember { get; set; }
|
||||
|
||||
public IDictionary<string, string> ToIdentityTokenRequest()
|
||||
{
|
||||
@@ -24,11 +24,6 @@ namespace Bit.App.Models.Api
|
||||
{ "client_id", "mobile" }
|
||||
};
|
||||
|
||||
if(OldAuthBearer != null)
|
||||
{
|
||||
dict.Add("OldAuthBearer", OldAuthBearer);
|
||||
}
|
||||
|
||||
if(Device != null)
|
||||
{
|
||||
dict.Add("DeviceType", Device.Type.ToString());
|
||||
@@ -40,7 +35,8 @@ namespace Bit.App.Models.Api
|
||||
if(Token != null && Provider.HasValue)
|
||||
{
|
||||
dict.Add("TwoFactorToken", Token);
|
||||
dict.Add("TwoFactorProvider", Provider.Value.ToString());
|
||||
dict.Add("TwoFactorProvider", ((byte)(Provider.Value)).ToString());
|
||||
dict.Add("TwoFactorRemember", Remember ? "1" : "0");
|
||||
}
|
||||
|
||||
return dict;
|
||||
|
||||
8
src/App/Models/Api/Request/TwoFactorEmailRequest.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
public class TwoFactorEmailRequest
|
||||
{
|
||||
public string Email { get; set; }
|
||||
public string MasterPasswordHash { get; set; }
|
||||
}
|
||||
}
|
||||
11
src/App/Models/Api/Response/AttachmentResponse.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
public class AttachmentResponse
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string Size { get; set; }
|
||||
public string SizeName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
public class CipherHistoryResponse
|
||||
{
|
||||
public IEnumerable<CipherResponse> Revised { get; set; }
|
||||
public IEnumerable<string> Deleted { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Bit.App.Enums;
|
||||
using System;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
@@ -12,7 +13,10 @@ namespace Bit.App.Models.Api
|
||||
public string OrganizationId { get; set; }
|
||||
public CipherType Type { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public bool Edit { get; set; }
|
||||
public bool OrganizationUseTotp { get; set; }
|
||||
public JObject Data { get; set; }
|
||||
public IEnumerable<AttachmentResponse> Attachments { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
@@ -13,7 +14,11 @@ namespace Bit.App.Models.Api
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Totp { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public bool Edit { get; set; }
|
||||
public bool OrganizationUseTotp { get; set; }
|
||||
public IEnumerable<AttachmentResponse> Attachments { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ namespace Bit.App.Models.Api
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool UseGroups { get; set; }
|
||||
public bool UseDirectory { get; set; }
|
||||
public bool UseTotp { get; set; }
|
||||
public int Seats { get; set; }
|
||||
public int MaxCollections { get; set; }
|
||||
public short? MaxStorageGb { get; set; }
|
||||
public string Key { get; set; }
|
||||
public OrganizationUserStatusType Status { get; set; }
|
||||
public OrganizationUserType Type { get; set; }
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace Bit.App.Models.Api
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public bool EmailVerified { get; set; }
|
||||
public bool Premium { get; set; }
|
||||
public string MasterPasswordHint { get; set; }
|
||||
public string Culture { get; set; }
|
||||
public bool TwoFactorEnabled { get; set; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json;
|
||||
using Bit.App.Enums;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
@@ -13,8 +14,9 @@ namespace Bit.App.Models.Api
|
||||
public string RefreshToken { get; set; }
|
||||
[JsonProperty("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
public List<int> TwoFactorProviders { get; set; }
|
||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders2 { get; set; }
|
||||
public string PrivateKey { get; set; }
|
||||
public string TwoFactorToken { get; set; }
|
||||
public string Key { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
51
src/App/Models/Attachment.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Bit.App.Models.Api;
|
||||
using Bit.App.Models.Data;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class Attachment
|
||||
{
|
||||
public Attachment()
|
||||
{ }
|
||||
|
||||
public Attachment(AttachmentData data)
|
||||
{
|
||||
Id = data.Id;
|
||||
Url = data.Url;
|
||||
FileName = data.FileName != null ? new CipherString(data.FileName) : null;
|
||||
SetSize(data.Size);
|
||||
SizeName = data.SizeName;
|
||||
}
|
||||
|
||||
public Attachment(AttachmentResponse response)
|
||||
{
|
||||
Id = response.Id;
|
||||
Url = response.Url;
|
||||
FileName = response.FileName != null ? new CipherString(response.FileName) : null;
|
||||
SetSize(response.Size);
|
||||
SizeName = response.SizeName;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string Url { get; set; }
|
||||
public CipherString FileName { get; set; }
|
||||
public long Size { get; set; }
|
||||
public string SizeName { get; set; }
|
||||
|
||||
public AttachmentData ToAttachmentData(string loginId)
|
||||
{
|
||||
return new AttachmentData(this, loginId);
|
||||
}
|
||||
|
||||
private void SetSize(string sizeString)
|
||||
{
|
||||
long size;
|
||||
if(!long.TryParse(sizeString, out size))
|
||||
{
|
||||
size = 0;
|
||||
}
|
||||
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public abstract class Cipher
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public CipherString Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,15 @@ namespace Bit.App.Models
|
||||
}
|
||||
CipherText = encPieces[0];
|
||||
break;
|
||||
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
|
||||
if(encPieces.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Malformed encPieces.");
|
||||
}
|
||||
CipherText = encPieces[0];
|
||||
Mac = encPieces[1];
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unknown encType.");
|
||||
}
|
||||
|
||||
47
src/App/Models/Data/AttachmentData.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using SQLite;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models.Api;
|
||||
|
||||
namespace Bit.App.Models.Data
|
||||
{
|
||||
[Table("Attachment")]
|
||||
public class AttachmentData : IDataObject<string>
|
||||
{
|
||||
public AttachmentData()
|
||||
{ }
|
||||
|
||||
public AttachmentData(Attachment attachment, string loginId)
|
||||
{
|
||||
Id = attachment.Id;
|
||||
LoginId = loginId;
|
||||
Url = attachment.Url;
|
||||
FileName = attachment.FileName?.EncryptedString;
|
||||
Size = attachment.Size.ToString();
|
||||
SizeName = attachment.SizeName;
|
||||
}
|
||||
|
||||
public AttachmentData(AttachmentResponse response, string loginId)
|
||||
{
|
||||
Id = response.Id;
|
||||
LoginId = loginId;
|
||||
Url = response.Url;
|
||||
FileName = response.FileName;
|
||||
Size = response.Size;
|
||||
SizeName = response.SizeName;
|
||||
}
|
||||
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; }
|
||||
[Indexed]
|
||||
public string LoginId { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string Size { get; set; }
|
||||
public string SizeName { get; set; }
|
||||
|
||||
public Attachment ToAttachment()
|
||||
{
|
||||
return new Attachment(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,10 @@ namespace Bit.App.Models.Data
|
||||
Username = login.Username?.EncryptedString;
|
||||
Password = login.Password?.EncryptedString;
|
||||
Notes = login.Notes?.EncryptedString;
|
||||
Totp = login?.Notes?.EncryptedString;
|
||||
Favorite = login.Favorite;
|
||||
Edit = login.Edit;
|
||||
OrganizationUseTotp = login.OrganizationUseTotp;
|
||||
}
|
||||
|
||||
public LoginData(LoginResponse login, string userId)
|
||||
@@ -36,8 +39,11 @@ namespace Bit.App.Models.Data
|
||||
Username = login.Username;
|
||||
Password = login.Password;
|
||||
Notes = login.Notes;
|
||||
Totp = login.Totp;
|
||||
Favorite = login.Favorite;
|
||||
RevisionDateTime = login.RevisionDate;
|
||||
Edit = login.Edit;
|
||||
OrganizationUseTotp = login.OrganizationUseTotp;
|
||||
}
|
||||
|
||||
public LoginData(CipherResponse cipher, string userId)
|
||||
@@ -58,7 +64,10 @@ namespace Bit.App.Models.Data
|
||||
Username = data.Username;
|
||||
Password = data.Password;
|
||||
Notes = data.Notes;
|
||||
Totp = data.Totp;
|
||||
Favorite = cipher.Favorite;
|
||||
Edit = cipher.Edit;
|
||||
OrganizationUseTotp = cipher.OrganizationUseTotp;
|
||||
RevisionDateTime = cipher.RevisionDate;
|
||||
}
|
||||
|
||||
@@ -73,7 +82,10 @@ namespace Bit.App.Models.Data
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Totp { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public bool Edit { get; set; }
|
||||
public bool OrganizationUseTotp { get; set; }
|
||||
public DateTime RevisionDateTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public Login ToLogin()
|
||||
|
||||
@@ -3,7 +3,7 @@ using Bit.App.Models.Api;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class Folder : Cipher
|
||||
public class Folder
|
||||
{
|
||||
public Folder()
|
||||
{ }
|
||||
@@ -20,6 +20,9 @@ namespace Bit.App.Models
|
||||
Name = response.Name != null ? new CipherString(response.Name) : null;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public CipherString Name { get; set; }
|
||||
|
||||
public FolderRequest ToFolderRequest()
|
||||
{
|
||||
return new FolderRequest(this);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
using Bit.App.Models.Api;
|
||||
using Bit.App.Models.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class Login : Cipher
|
||||
public class Login
|
||||
{
|
||||
public Login()
|
||||
{ }
|
||||
|
||||
public Login(LoginData data)
|
||||
public Login(LoginData data, IEnumerable<AttachmentData> attachments = null)
|
||||
{
|
||||
Id = data.Id;
|
||||
UserId = data.UserId;
|
||||
@@ -19,7 +21,11 @@ namespace Bit.App.Models
|
||||
Username = data.Username != null ? new CipherString(data.Username) : null;
|
||||
Password = data.Password != null ? new CipherString(data.Password) : null;
|
||||
Notes = data.Notes != null ? new CipherString(data.Notes) : null;
|
||||
Totp = data.Totp != null ? new CipherString(data.Totp) : null;
|
||||
Favorite = data.Favorite;
|
||||
Edit = data.Edit;
|
||||
OrganizationUseTotp = data.OrganizationUseTotp;
|
||||
Attachments = attachments?.Select(a => new Attachment(a));
|
||||
}
|
||||
|
||||
public Login(LoginResponse response)
|
||||
@@ -33,17 +39,27 @@ namespace Bit.App.Models
|
||||
Username = response.Username != null ? new CipherString(response.Username) : null;
|
||||
Password = response.Password != null ? new CipherString(response.Password) : null;
|
||||
Notes = response.Notes != null ? new CipherString(response.Notes) : null;
|
||||
Totp = response.Totp != null ? new CipherString(response.Totp) : null;
|
||||
Favorite = response.Favorite;
|
||||
Edit = response.Edit;
|
||||
OrganizationUseTotp = response.OrganizationUseTotp;
|
||||
Attachments = response.Attachments?.Select(a => new Attachment(a));
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public string FolderId { get; set; }
|
||||
public CipherString Name { get; set; }
|
||||
public CipherString Uri { get; set; }
|
||||
public CipherString Username { get; set; }
|
||||
public CipherString Password { get; set; }
|
||||
public CipherString Notes { get; set; }
|
||||
public CipherString Totp { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public bool Edit { get; set; }
|
||||
public bool OrganizationUseTotp { get; set; }
|
||||
public IEnumerable<Attachment> Attachments { get; set; }
|
||||
|
||||
public LoginRequest ToLoginRequest()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace Bit.App.Models
|
||||
using Bit.App.Enums;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class LoginResult
|
||||
{
|
||||
@@ -8,7 +11,8 @@
|
||||
|
||||
public class FullLoginResult : LoginResult
|
||||
{
|
||||
public bool TwoFactorRequired { get; set; }
|
||||
public bool TwoFactorRequired => TwoFactorProviders != null && TwoFactorProviders.Count > 0;
|
||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
||||
public SymmetricCryptoKey Key { get; set; }
|
||||
public string MasterPasswordHash { get; set; }
|
||||
}
|
||||
|
||||
25
src/App/Models/Page/VaultAttachmentsPageModel.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Page
|
||||
{
|
||||
public class VaultAttachmentsPageModel
|
||||
{
|
||||
public class Attachment : List<Attachment>
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string SizeName { get; set; }
|
||||
public long Size { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
public Attachment(Models.Attachment attachment)
|
||||
{
|
||||
Id = attachment.Id;
|
||||
Name = attachment.FileName?.Decrypt();
|
||||
SizeName = attachment.SizeName;
|
||||
Size = attachment.Size;
|
||||
Url = attachment.Url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Bit.App.Resources;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.App.Models.Page
|
||||
{
|
||||
@@ -12,20 +13,24 @@ namespace Bit.App.Models.Page
|
||||
{
|
||||
Id = login.Id;
|
||||
Shared = !string.IsNullOrWhiteSpace(login.OrganizationId);
|
||||
HasAttachments = login.Attachments?.Any() ?? false;
|
||||
FolderId = login.FolderId;
|
||||
Name = login.Name?.Decrypt(login.OrganizationId);
|
||||
Username = login.Username?.Decrypt(login.OrganizationId) ?? " ";
|
||||
Password = new Lazy<string>(() => login.Password?.Decrypt(login.OrganizationId));
|
||||
Uri = new Lazy<string>(() => login.Uri?.Decrypt(login.OrganizationId));
|
||||
Totp = new Lazy<string>(() => login.Totp?.Decrypt(login.OrganizationId));
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public bool Shared { get; set; }
|
||||
public bool HasAttachments { get; set; }
|
||||
public string FolderId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Username { get; set; }
|
||||
public Lazy<string> Password { get; set; }
|
||||
public Lazy<string> Uri { get; set; }
|
||||
public Lazy<string> Totp { get; set; }
|
||||
}
|
||||
|
||||
public class AutofillLogin : Login
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.ComponentModel;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Page
|
||||
{
|
||||
@@ -12,7 +13,10 @@ namespace Bit.App.Models.Page
|
||||
private string _password;
|
||||
private string _uri;
|
||||
private string _notes;
|
||||
private string _totpCode;
|
||||
private int _totpSec = 30;
|
||||
private bool _revealPassword;
|
||||
private List<Attachment> _attachments;
|
||||
|
||||
public VaultViewLoginPageModel() { }
|
||||
|
||||
@@ -35,31 +39,9 @@ namespace Bit.App.Models.Page
|
||||
_username = value;
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Username)));
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowUsername)));
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(UsernameFontSize)));
|
||||
}
|
||||
}
|
||||
public bool ShowUsername => !string.IsNullOrWhiteSpace(Username);
|
||||
public double UsernameFontSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
var length = Username?.Length ?? 0;
|
||||
|
||||
if(length > 35)
|
||||
{
|
||||
return Device.GetNamedSize(NamedSize.Micro, typeof(Label));
|
||||
}
|
||||
else if(length > 25)
|
||||
{
|
||||
return Device.GetNamedSize(NamedSize.Small, typeof(Label));
|
||||
}
|
||||
}
|
||||
|
||||
return Device.GetNamedSize(NamedSize.Medium, typeof(Label));
|
||||
}
|
||||
}
|
||||
|
||||
public string Password
|
||||
{
|
||||
@@ -70,31 +52,9 @@ namespace Bit.App.Models.Page
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Password)));
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaskedPassword)));
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowPassword)));
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(PasswordFontSize)));
|
||||
}
|
||||
}
|
||||
public bool ShowPassword => !string.IsNullOrWhiteSpace(Password);
|
||||
public double PasswordFontSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
var length = Password?.Length ?? 0;
|
||||
|
||||
if(length > 25)
|
||||
{
|
||||
return Device.GetNamedSize(NamedSize.Micro, typeof(Label));
|
||||
}
|
||||
else if(length > 20)
|
||||
{
|
||||
return Device.GetNamedSize(NamedSize.Small, typeof(Label));
|
||||
}
|
||||
}
|
||||
|
||||
return Device.GetNamedSize(NamedSize.Medium, typeof(Label));
|
||||
}
|
||||
}
|
||||
|
||||
public string Uri
|
||||
{
|
||||
@@ -119,6 +79,17 @@ namespace Bit.App.Models.Page
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android && !Uri.StartsWith("http") &&
|
||||
!Uri.StartsWith("androidapp://"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Device.RuntimePlatform != Device.Android && !Uri.StartsWith("http"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
if(!System.Uri.TryCreate(Uri, UriKind.Absolute, out uri))
|
||||
{
|
||||
@@ -181,6 +152,43 @@ namespace Bit.App.Models.Page
|
||||
public string ShowHideText => RevealPassword ? AppResources.Hide : AppResources.Show;
|
||||
public ImageSource ShowHideImage => RevealPassword ? ImageSource.FromFile("eye_slash") : ImageSource.FromFile("eye");
|
||||
|
||||
public string TotpCode
|
||||
{
|
||||
get { return _totpCode; }
|
||||
set
|
||||
{
|
||||
_totpCode = value;
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TotpCode)));
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TotpCodeFormatted)));
|
||||
}
|
||||
}
|
||||
public int TotpSecond
|
||||
{
|
||||
get { return _totpSec; }
|
||||
set
|
||||
{
|
||||
_totpSec = value;
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TotpSecond)));
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TotpColor)));
|
||||
}
|
||||
}
|
||||
public bool TotpLow => TotpSecond <= 7;
|
||||
public Color TotpColor => !string.IsNullOrWhiteSpace(TotpCode) && TotpLow ? Color.Red : Color.Black;
|
||||
public string TotpCodeFormatted => !string.IsNullOrWhiteSpace(TotpCode) ?
|
||||
string.Format("{0} {1}", TotpCode.Substring(0, 3), TotpCode.Substring(3)) : null;
|
||||
|
||||
public List<Attachment> Attachments
|
||||
{
|
||||
get { return _attachments; }
|
||||
set
|
||||
{
|
||||
_attachments = value;
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Attachments)));
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowAttachments)));
|
||||
}
|
||||
}
|
||||
public bool ShowAttachments => (Attachments?.Count ?? 0) > 0;
|
||||
|
||||
public void Update(Login login)
|
||||
{
|
||||
Name = login.Name?.Decrypt(login.OrganizationId);
|
||||
@@ -188,6 +196,36 @@ namespace Bit.App.Models.Page
|
||||
Password = login.Password?.Decrypt(login.OrganizationId);
|
||||
Uri = login.Uri?.Decrypt(login.OrganizationId);
|
||||
Notes = login.Notes?.Decrypt(login.OrganizationId);
|
||||
|
||||
if(login.Attachments != null)
|
||||
{
|
||||
var attachments = new List<Attachment>();
|
||||
foreach(var attachment in login.Attachments)
|
||||
{
|
||||
attachments.Add(new Attachment
|
||||
{
|
||||
Id = attachment.Id,
|
||||
Name = attachment.FileName?.Decrypt(login.OrganizationId),
|
||||
SizeName = attachment.SizeName,
|
||||
Size = attachment.Size,
|
||||
Url = attachment.Url
|
||||
});
|
||||
}
|
||||
Attachments = attachments;
|
||||
}
|
||||
else
|
||||
{
|
||||
login.Attachments = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class Attachment
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string SizeName { get; set; }
|
||||
public long Size { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
239
src/App/Pages/EnvironmentPage.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Acr.UserDialogs;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class EnvironmentPage : ExtendedContentPage
|
||||
{
|
||||
private IAppSettingsService _appSettings;
|
||||
private IUserDialogs _userDialogs;
|
||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||
|
||||
public EnvironmentPage()
|
||||
: base(updateActivity: false)
|
||||
{
|
||||
_appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell BaseUrlCell { get; set; }
|
||||
public FormEntryCell WebVaultUrlCell { get; set; }
|
||||
public FormEntryCell ApiUrlCell { get; set; }
|
||||
public FormEntryCell IdentityUrlCell { get; set; }
|
||||
public StackLayout StackLayout { get; set; }
|
||||
public Label SelfHostLabel { get; set; }
|
||||
public Label CustomLabel { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
|
||||
IdentityUrlCell = new FormEntryCell(AppResources.IdentityUrl, entryKeyboard: Keyboard.Url);
|
||||
IdentityUrlCell.Entry.Text = _appSettings.IdentityUrl;
|
||||
ApiUrlCell = new FormEntryCell(AppResources.ApiUrl, nextElement: IdentityUrlCell.Entry, entryKeyboard: Keyboard.Url);
|
||||
ApiUrlCell.Entry.Text = _appSettings.ApiUrl;
|
||||
WebVaultUrlCell = new FormEntryCell(AppResources.WebVaultUrl, nextElement: ApiUrlCell.Entry, entryKeyboard: Keyboard.Url);
|
||||
WebVaultUrlCell.Entry.Text = _appSettings.WebVaultUrl;
|
||||
BaseUrlCell = new FormEntryCell(AppResources.ServerUrl, nextElement: WebVaultUrlCell.Entry, entryKeyboard: Keyboard.Url);
|
||||
BaseUrlCell.Entry.Text = _appSettings.BaseUrl;
|
||||
|
||||
var table = new FormTableView
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.SelfHostedEnvironment)
|
||||
{
|
||||
BaseUrlCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SelfHostLabel = new Label
|
||||
{
|
||||
Text = AppResources.SelfHostedEnvironmentFooter,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
|
||||
};
|
||||
|
||||
var table2 = new FormTableView
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.CustomEnvironment)
|
||||
{
|
||||
WebVaultUrlCell,
|
||||
ApiUrlCell,
|
||||
IdentityUrlCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CustomLabel = new Label
|
||||
{
|
||||
Text = AppResources.CustomEnvironmentFooter,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
|
||||
};
|
||||
|
||||
StackLayout = new StackLayout
|
||||
{
|
||||
Children = { table, SelfHostLabel, table2, CustomLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
var scrollView = new ScrollView
|
||||
{
|
||||
Content = StackLayout
|
||||
};
|
||||
|
||||
var toolbarItem = new ToolbarItem(AppResources.Save, null, async () => await SaveAsync(),
|
||||
ToolbarItemOrder.Default, 0);
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = table2.RowHeight = -1;
|
||||
table.EstimatedRowHeight = table2.EstimatedRowHeight = 70;
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close, () =>
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
}));
|
||||
}
|
||||
|
||||
ToolbarItems.Add(toolbarItem);
|
||||
Title = AppResources.Settings;
|
||||
Content = scrollView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
BaseUrlCell.InitEvents();
|
||||
IdentityUrlCell.InitEvents();
|
||||
ApiUrlCell.InitEvents();
|
||||
WebVaultUrlCell.InitEvents();
|
||||
StackLayout.LayoutChanged += Layout_LayoutChanged;
|
||||
BaseUrlCell.Entry.FocusWithDelay();
|
||||
}
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
BaseUrlCell.Dispose();
|
||||
IdentityUrlCell.Dispose();
|
||||
ApiUrlCell.Dispose();
|
||||
WebVaultUrlCell.Dispose();
|
||||
StackLayout.LayoutChanged -= Layout_LayoutChanged;
|
||||
}
|
||||
|
||||
private void Layout_LayoutChanged(object sender, EventArgs e)
|
||||
{
|
||||
SelfHostLabel.WidthRequest = StackLayout.Bounds.Width - SelfHostLabel.Bounds.Left * 2;
|
||||
CustomLabel.WidthRequest = StackLayout.Bounds.Width - CustomLabel.Bounds.Left * 2;
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
Uri result;
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(BaseUrlCell.Entry.Text))
|
||||
{
|
||||
BaseUrlCell.Entry.Text = FixUrl(BaseUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(BaseUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
_userDialogs.Alert(string.Format(AppResources.FormattedIncorrectly, AppResources.ServerUrl));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(WebVaultUrlCell.Entry.Text))
|
||||
{
|
||||
WebVaultUrlCell.Entry.Text = FixUrl(WebVaultUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(WebVaultUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
_userDialogs.Alert(string.Format(AppResources.FormattedIncorrectly, AppResources.WebVaultUrl));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WebVaultUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(ApiUrlCell.Entry.Text))
|
||||
{
|
||||
ApiUrlCell.Entry.Text = FixUrl(ApiUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(ApiUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
_userDialogs.Alert(string.Format(AppResources.FormattedIncorrectly, AppResources.ApiUrl));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ApiUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(IdentityUrlCell.Entry.Text))
|
||||
{
|
||||
IdentityUrlCell.Entry.Text = FixUrl(IdentityUrlCell.Entry.Text);
|
||||
if(!Uri.TryCreate(IdentityUrlCell.Entry.Text, UriKind.Absolute, out result))
|
||||
{
|
||||
_userDialogs.Alert(string.Format(AppResources.FormattedIncorrectly, AppResources.IdentityUrl));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IdentityUrlCell.Entry.Text = null;
|
||||
}
|
||||
|
||||
_appSettings.BaseUrl = BaseUrlCell.Entry.Text;
|
||||
_appSettings.IdentityUrl = IdentityUrlCell.Entry.Text;
|
||||
_appSettings.ApiUrl = ApiUrlCell.Entry.Text;
|
||||
_appSettings.WebVaultUrl = WebVaultUrlCell.Entry.Text;
|
||||
_userDialogs.Toast(AppResources.EnvironmentSaved);
|
||||
_googleAnalyticsService.TrackAppEvent("SetEnvironmentUrls");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
|
||||
private string FixUrl(string url)
|
||||
{
|
||||
url = url.TrimEnd('/');
|
||||
if(!url.StartsWith("http://") && !url.StartsWith("https://"))
|
||||
{
|
||||
url = $"https://{url}";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private class FormTableView : ExtendedTableView
|
||||
{
|
||||
public FormTableView()
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
EnableScrolling = false;
|
||||
HasUnevenRows = true;
|
||||
EnableSelection = true;
|
||||
VerticalOptions = LayoutOptions.Start;
|
||||
NoFooter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,25 @@ namespace Bit.App.Pages
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
|
||||
var settingsButton = new Button
|
||||
{
|
||||
Image = "cog",
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
WidthRequest = 25,
|
||||
HeightRequest = 25,
|
||||
BackgroundColor = Color.Transparent,
|
||||
Margin = new Thickness(-20, -30, 0, 0),
|
||||
Command = new Command(async () => await SettingsAsync())
|
||||
};
|
||||
|
||||
var logo = new CachedImage
|
||||
{
|
||||
Source = "logo",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 282,
|
||||
Margin = new Thickness(0, 30, 0, 0),
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
@@ -77,7 +90,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
Padding = new Thickness(30, 40),
|
||||
Spacing = 10,
|
||||
Children = { logo, message, createAccountButton, loginButton }
|
||||
Children = { settingsButton, logo, message, createAccountButton, loginButton }
|
||||
};
|
||||
|
||||
Title = AppResources.Bitwarden;
|
||||
@@ -119,5 +132,16 @@ namespace Bit.App.Pages
|
||||
await Navigation.PushForDeviceAsync(new LoginPage(email));
|
||||
_userDialogs.Toast(AppResources.AccountCreated);
|
||||
}
|
||||
|
||||
public async Task SettingsAsync()
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
await Navigation.PushForDeviceAsync(new EnvironmentPage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +100,31 @@ namespace Bit.App.Pages
|
||||
{
|
||||
base.OnAppearing();
|
||||
PasswordCell.InitEvents();
|
||||
PasswordCell.Entry.FocusWithDelay();
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
if(!PasswordCell.Entry.IsFocused)
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() => PasswordCell.Entry.FocusWithDelay());
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
PasswordCell.Entry.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
|
||||
@@ -5,6 +5,7 @@ using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Controls;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -79,7 +80,30 @@ namespace Bit.App.Pages
|
||||
_tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.InitEvents();
|
||||
PinControl.Entry.FocusWithDelay();
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
if(!PinControl.Entry.IsFocused)
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() => PinControl.Entry.Focus());
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
PinControl.Entry.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
|
||||