Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f04b465f3 | ||
|
|
3f5c8fe2cb | ||
|
|
d1cf6c68f3 | ||
|
|
7117f00480 | ||
|
|
d4f37343a2 | ||
|
|
71e15e9cab | ||
|
|
eadf00feba | ||
|
|
95f28fad2b | ||
|
|
9753137a72 | ||
|
|
e4f7436dfb | ||
|
|
d39211310d | ||
|
|
0a6fb3ec0a | ||
|
|
5232cf7cec | ||
|
|
6e16ffe05f | ||
|
|
2d6895aeea | ||
|
|
b5311e1448 | ||
|
|
cc63eb383d | ||
|
|
01736ca685 | ||
|
|
be47bb7263 | ||
|
|
bcb7d88ed7 | ||
|
|
cf58c1b4b5 | ||
|
|
70c57928e7 | ||
|
|
c8219b29c0 | ||
|
|
15a9f80430 | ||
|
|
0684dfe869 | ||
|
|
83a89566ac | ||
|
|
04f486b003 | ||
|
|
f135c92434 | ||
|
|
78b095d01a | ||
|
|
1b8bd494e2 | ||
|
|
481925ac78 | ||
|
|
4854b2b1c0 | ||
|
|
2d7b33459e | ||
|
|
27e0c7421b | ||
|
|
b26c3d050c | ||
|
|
439370e25a | ||
|
|
1be4f6e20c | ||
|
|
2714c7cce9 | ||
|
|
952935de23 | ||
|
|
bdb8b5ea39 | ||
|
|
3ad4e28a2c | ||
|
|
48d0d068d1 | ||
|
|
56e166d61a | ||
|
|
672d753adf | ||
|
|
0d9ba92db4 | ||
|
|
8cf25d3602 | ||
|
|
a6bc44dc10 | ||
|
|
408d66ee74 | ||
|
|
b136bb74b8 | ||
|
|
18b2b6f447 | ||
|
|
490d1775a2 | ||
|
|
458de2d2e0 | ||
|
|
51ae3fc62f | ||
|
|
58c5c55d09 | ||
|
|
4c2bcb9e6b | ||
|
|
498379bb7e | ||
|
|
e7f3b115a4 | ||
|
|
0ebfe85d8e | ||
|
|
8e29a990cb | ||
|
|
a960ccd786 | ||
|
|
6b86e836d7 | ||
|
|
fb35b9b10a | ||
|
|
a45773e1ca | ||
|
|
2405a6f21e | ||
|
|
533dd6135e | ||
|
|
efc25543ca | ||
|
|
82d4745da3 | ||
|
|
ac6e95c442 | ||
|
|
375f23ac9e | ||
|
|
8e5a01d82c | ||
|
|
910658aa93 | ||
|
|
d766ffa040 | ||
|
|
b960640e03 | ||
|
|
c984617b1c | ||
|
|
a12a7127c0 | ||
|
|
98a6a5c93d | ||
|
|
27202fd740 | ||
|
|
c01d02de27 | ||
|
|
1d23bcc979 | ||
|
|
ac8abdaa17 | ||
|
|
613977c6f9 | ||
|
|
54159c9d05 | ||
|
|
8d5d477b4a | ||
|
|
2c73906ad3 | ||
|
|
079fb34120 | ||
|
|
17ed1cdc00 | ||
|
|
d53ea584ba | ||
|
|
b435256911 | ||
|
|
27e996dba0 | ||
|
|
22f3bd1073 | ||
|
|
fb564fa817 | ||
|
|
be9db2930f | ||
|
|
5bce95a686 | ||
|
|
f6ca9b9d0f | ||
|
|
88f186907b | ||
|
|
9faf1d9de5 | ||
|
|
8b1d1d0f6d | ||
|
|
8c19e2c3f2 | ||
|
|
d2d8ee504d | ||
|
|
d96b279beb | ||
|
|
f5e7f9249c | ||
|
|
56c33ee82b | ||
|
|
b05dd4cc2c | ||
|
|
36d4ce8718 | ||
|
|
ddec7ab643 | ||
|
|
75201c9b30 | ||
|
|
99c81e5a5d | ||
|
|
b84ad39133 | ||
|
|
4a19e2b673 | ||
|
|
475c3559f6 |
@@ -1,9 +1,9 @@
|
||||
[] (https://ci.appveyor.com/project/bitwarden/mobile)
|
||||
[](https://ci.appveyor.com/project/bitwarden/mobile)
|
||||
[](https://gitter.im/bitwarden/Lobby)
|
||||
|
||||
# bitwarden mobile
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://developer.android.com/images/brand/en_generic_rgb_wo_45.png"width="129" height="45"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://linkmaker.itunes.apple.com/images/badges/en-us/badge_appstore-lrg.svg" width="165" height="40"></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a> <a href="https://www.amazon.com/dp/B06XMYGPMV" target="_blank"><img src="https://imgur.com/f75uYeM.png" width="132" height="45"></a>
|
||||
|
||||
The bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
|
||||
|
||||
@@ -14,7 +14,7 @@ The bitwarden mobile application is written in C# with Xamarin Android, Xamarin
|
||||
- [Visual Studio w/ Xamarin -or- Xamarin Studio](https://store.xamarin.com/)
|
||||
|
||||
By default the app is targeting the production API. If you are running the [Core](https://github.com/bitwarden/core) API locally,
|
||||
you'll need to switch the extension to target your local API. Open `src/App/Utilities/ApiHttpClient.cs` and set `BaseAddress` to your local
|
||||
you'll need to switch the app to target your local API. Open `src/App/Utilities/ApiHttpClient.cs` and set `BaseAddress` to your local
|
||||
API instance (ex. `new Uri("http://localhost:4000")`).
|
||||
|
||||
After restoring the nuget packages, you can now build and run the app.
|
||||
|
||||
14
appveyor.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
skip_tags: true
|
||||
before_build:
|
||||
- nuget restore
|
||||
- IF DEFINED keystore_dec_secret nuget install secure-file -ExcludeVersion
|
||||
after_build:
|
||||
- ps: IF($env:keystore_dec_secret) { .\src\Android\increment-version.ps1 $($env:APPVEYOR_BUILD_FOLDER) $($env:APPVEYOR_BUILD_NUMBER) }
|
||||
- IF DEFINED keystore_dec_secret secure-file\tools\secure-file -decrypt src\Android\8bit.keystore.enc -secret %keystore_dec_secret%
|
||||
- IF DEFINED keystore_password msbuild "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" "/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=%keystore_password%" "/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=%keystore_password%" "src\Android\Android.csproj"
|
||||
- ps: IF($env:keystore_dec_secret) { copy-item src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk .\com.x8bit.bitwarden-$($env:APPVEYOR_BUILD_NUMBER).apk }
|
||||
on_success:
|
||||
- IF DEFINED play_dec_secret secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret %play_dec_secret%
|
||||
- IF DEFINED play_dec_secret store\google\Publisher\bin\Debug\Publisher.exe %APPVEYOR_BUILD_FOLDER%\store\google\Publisher\play_creds.json %APPVEYOR_BUILD_FOLDER%\src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk alpha
|
||||
artifacts:
|
||||
- path: com.x8bit.bitwarden-%APPVEYOR_BUILD_NUMBER%.apk
|
||||
@@ -17,7 +17,7 @@
|
||||
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
|
||||
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v7.1</TargetFrameworkVersion>
|
||||
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
|
||||
<AndroidStoreUncompressedFileExtensions />
|
||||
<MandroidI18n />
|
||||
@@ -76,12 +76,10 @@
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Acr.UserDialogs, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\MonoAndroid10\Acr.UserDialogs.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.10\lib\MonoAndroid10\Acr.UserDialogs.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Acr.UserDialogs.Interface, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\MonoAndroid10\Acr.UserDialogs.Interface.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.10\lib\MonoAndroid10\Acr.UserDialogs.Interface.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AndHUD, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\AndHUD.1.2.0\lib\MonoAndroid\AndHUD.dll</HintPath>
|
||||
@@ -91,17 +89,26 @@
|
||||
<HintPath>..\..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.9\lib\MonoAndroid10\FFImageLoading.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Forms, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.9\lib\MonoAndroid10\FFImageLoading.Forms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Forms.Droid, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.9\lib\MonoAndroid10\FFImageLoading.Forms.Droid.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<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.3.180\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="HockeySDK, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.0\lib\MonoAndroid403\HockeySDK.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\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.0\lib\MonoAndroid403\HockeySDK.AndroidBindings.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.2\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>
|
||||
@@ -137,13 +144,11 @@
|
||||
<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>
|
||||
<Reference Include="Plugin.Connectivity, Version=2.2.12.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\MonoAndroid10\Plugin.Connectivity.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="Plugin.Connectivity.Abstractions, Version=2.2.12.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<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>
|
||||
@@ -157,13 +162,11 @@
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.2.0\lib\MonoAndroid\Plugin.Fingerprint.Abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Settings, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\MonoAndroid10\Plugin.Settings.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\MonoAndroid10\Plugin.Settings.Abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<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>
|
||||
@@ -253,20 +256,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.3.180\lib\MonoAndroid10\Xamarin.Forms.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\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.3.180\lib\MonoAndroid10\Xamarin.Forms.Platform.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\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.3.180\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\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.3.180\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\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>
|
||||
@@ -305,7 +304,6 @@
|
||||
<Compile Include="Controls\ExtendedButtonRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedTabbedPageRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedTableViewRenderer.cs" />
|
||||
<Compile Include="CustomAndroidClientHandler.cs" />
|
||||
<Compile Include="HockeyAppCrashManagerListener.cs" />
|
||||
<Compile Include="AutofillService.cs" />
|
||||
<Compile Include="Controls\ExtendedEditorRenderer.cs" />
|
||||
@@ -327,6 +325,7 @@
|
||||
<Compile Include="MainActivity.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\LogService.cs" />
|
||||
<Compile Include="Services\MemoryService.cs" />
|
||||
<Compile Include="Services\ReflectionService.cs" />
|
||||
<Compile Include="Services\SqlService.cs" />
|
||||
<Compile Include="SplashActivity.cs" />
|
||||
@@ -745,9 +744,6 @@
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\icon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidEnvironment Include="EnvironmentVariables.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\notification_sm.png" />
|
||||
</ItemGroup>
|
||||
@@ -815,19 +811,49 @@
|
||||
<AndroidResource Include="Resources\values\strings.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\close.png" />
|
||||
<AndroidResource Include="Resources\drawable\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\close.png" />
|
||||
<AndroidResource Include="Resources\drawable-hdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\close.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\close.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\close.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\share.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\share.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\share.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\share.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\share.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\share_tools.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\share_tools.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\share_tools.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\share_tools.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\share_tools.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')" />
|
||||
@@ -837,10 +863,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.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.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'))" />
|
||||
</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.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.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')" />
|
||||
<!-- 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">
|
||||
|
||||
@@ -94,8 +94,10 @@ namespace Bit.Android
|
||||
}
|
||||
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
intent.PutExtra("uri", _lastQueriedUri);
|
||||
intent.PutExtra("ts", Java.Lang.JavaSystem.CurrentTimeMillis());
|
||||
if(!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
|
||||
{
|
||||
intent.PutExtra("uri", _lastQueriedUri);
|
||||
}
|
||||
StartActivityForResult(intent, requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,91 +19,111 @@ namespace Bit.Android
|
||||
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
||||
private const string BitwardenWebsite = "bitwarden.com";
|
||||
|
||||
public static bool Enabled { get; set; } = false;
|
||||
private static Dictionary<string, string[]> BrowserPackages => new Dictionary<string, string[]>
|
||||
private static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
||||
{
|
||||
{ "com.android.chrome", new string[] { "url_bar" } },
|
||||
{ "com.chrome.beta", new string[] { "url_bar" } },
|
||||
{ "com.android.browser", new string[] { "url" } },
|
||||
{ "com.brave.browser", new string[] { "url_bar" } },
|
||||
{ "com.opera.browser", new string[] { "url_field" } },
|
||||
{ "com.opera.browser.beta", new string[] { "url_field" } },
|
||||
{ "com.opera.mini.native", new string[] { "url_field" } },
|
||||
{ "com.chrome.dev", new string[] { "url_bar" } },
|
||||
{ "com.chrome.canary", new string[] { "url_bar" } },
|
||||
{ "com.google.android.apps.chrome", new string[] { "url_bar" } },
|
||||
{ "com.google.android.apps.chrome_dev", new string[] { "url_bar" } },
|
||||
{ "org.iron.srware", new string[] { "url_bar" } },
|
||||
{ "com.sec.android.app.sbrowser", new string[] { "sbrowser_url_bar" } },
|
||||
{ "com.yandex.browser", new string[] { "bro_common_omnibox_host", "bro_common_omnibox_edit_text" } },
|
||||
{ "org.mozilla.firefox", new string[] { "url_bar_title" } },
|
||||
{ "org.mozilla.firefox_beta", new string[] { "url_bar_title" } },
|
||||
{ "com.ghostery.android.ghostery",new string[] { "search_field" } },
|
||||
{ "org.adblockplus.browser", new string[] { "url_bar_title" } },
|
||||
{ "com.htc.sense.browser", new string[] { "title" } },
|
||||
{ "com.amazon.cloud9", new string[] { "url" } },
|
||||
{ "mobi.mgeek.TunnyBrowser", new string[] { "title" } },
|
||||
{ "com.nubelacorp.javelin", new string[] { "enterUrl" } },
|
||||
{ "com.jerky.browser2", new string[] { "enterUrl" } },
|
||||
{ "com.mx.browser", new string[] { "address_editor_with_progress" } },
|
||||
{ "com.mx.browser.tablet", new string[] { "address_editor_with_progress"} },
|
||||
{ "com.linkbubble.playstore", new string[] { "url_text" }}
|
||||
};
|
||||
new Browser("com.android.chrome", "url_bar"),
|
||||
new Browser("com.chrome.beta", "url_bar"),
|
||||
new Browser("com.android.browser", "url"),
|
||||
new Browser("com.brave.browser", "url_bar"),
|
||||
new Browser("com.opera.browser", "url_field"),
|
||||
new Browser("com.opera.browser.beta", "url_field"),
|
||||
new Browser("com.opera.mini.native", "url_field"),
|
||||
new Browser("com.chrome.dev", "url_bar"),
|
||||
new Browser("com.chrome.canary", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||
new Browser("org.iron.srware", "url_bar"),
|
||||
new Browser("com.sec.android.app.sbrowser", "sbrowser_url_bar"),
|
||||
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("com.ghostery.android.ghostery", "search_field"),
|
||||
new Browser("org.adblockplus.browser", "url_bar_title"),
|
||||
new Browser("com.htc.sense.browser", "title"),
|
||||
new Browser("com.amazon.cloud9", "url"),
|
||||
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
||||
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
||||
new Browser("com.jerky.browser2", "enterUrl"),
|
||||
new Browser("com.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")
|
||||
}.ToDictionary(n => n.PackageName);
|
||||
|
||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||
{
|
||||
Enabled = true;
|
||||
var root = RootInActiveWindow;
|
||||
if(string.IsNullOrWhiteSpace(e.PackageName) || e.PackageName == SystemUiPackage ||
|
||||
root?.PackageName != e.PackageName)
|
||||
try
|
||||
{
|
||||
return;
|
||||
}
|
||||
var root = RootInActiveWindow;
|
||||
if(e == null || root == null || string.IsNullOrWhiteSpace(e.PackageName) ||
|
||||
e.PackageName == SystemUiPackage || root.PackageName != e.PackageName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch(e.EventType)
|
||||
{
|
||||
case EventTypes.WindowContentChanged:
|
||||
case EventTypes.WindowStateChanged:
|
||||
var cancelNotification = true;
|
||||
/*
|
||||
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();
|
||||
*/
|
||||
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
{
|
||||
CancelNotification();
|
||||
break;
|
||||
}
|
||||
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||
switch(e.EventType)
|
||||
{
|
||||
case EventTypes.WindowContentChanged:
|
||||
case EventTypes.WindowStateChanged:
|
||||
var cancelNotification = true;
|
||||
|
||||
var passwordNodes = GetWindowNodes(root, e, n => n.Password);
|
||||
if(passwordNodes.Any())
|
||||
{
|
||||
var uri = GetUri(root);
|
||||
if(uri.Contains(BitwardenWebsite))
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
{
|
||||
notificationManager?.Cancel(AutoFillNotificationId);
|
||||
break;
|
||||
}
|
||||
|
||||
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
||||
var passwordNodes = GetWindowNodes(root, e, n => n.Password, false);
|
||||
if(passwordNodes.Count > 0)
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n));
|
||||
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyToAutofill(uri);
|
||||
cancelNotification = false;
|
||||
var uri = GetUri(root);
|
||||
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||
{
|
||||
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
|
||||
allEditTexts.Dispose();
|
||||
usernameEditText.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyToAutofill(uri, notificationManager);
|
||||
cancelNotification = false;
|
||||
}
|
||||
}
|
||||
|
||||
AutofillActivity.LastCredentials = null;
|
||||
}
|
||||
|
||||
AutofillActivity.LastCredentials = null;
|
||||
}
|
||||
passwordNodes.Dispose();
|
||||
|
||||
if(cancelNotification)
|
||||
{
|
||||
CancelNotification();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if(cancelNotification)
|
||||
{
|
||||
notificationManager?.Cancel(AutoFillNotificationId);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
notificationManager?.Dispose();
|
||||
root.Dispose();
|
||||
e.Dispose();
|
||||
}
|
||||
// Some unknown condition is causing NullReferenceException's in production. Suppress it for now.
|
||||
catch(NullReferenceException) { }
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
@@ -111,64 +131,44 @@ namespace Bit.Android
|
||||
|
||||
}
|
||||
|
||||
protected override void OnServiceConnected()
|
||||
{
|
||||
base.OnServiceConnected();
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
private void CancelNotification()
|
||||
{
|
||||
var notificationManager = ((NotificationManager)GetSystemService(NotificationService));
|
||||
notificationManager.Cancel(AutoFillNotificationId);
|
||||
}
|
||||
|
||||
private string GetUri(AccessibilityNodeInfo root)
|
||||
{
|
||||
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
|
||||
if(BrowserPackages.ContainsKey(root.PackageName))
|
||||
if(SupportedBrowsers.ContainsKey(root.PackageName))
|
||||
{
|
||||
foreach(var addressViewId in BrowserPackages[root.PackageName])
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||
$"{root.PackageName}:id/{SupportedBrowsers[root.PackageName].UriViewId}").FirstOrDefault();
|
||||
if(addressNode != null)
|
||||
{
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||
$"{root.PackageName}:id/{addressViewId}").FirstOrDefault();
|
||||
if(addressNode == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uri = ExtractUri(uri, addressNode);
|
||||
break;
|
||||
uri = ExtractUri(uri, addressNode, SupportedBrowsers[root.PackageName]);
|
||||
addressNode.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode)
|
||||
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
||||
{
|
||||
if(addressNode != null)
|
||||
if(addressNode?.Text != null)
|
||||
{
|
||||
uri = addressNode.Text;
|
||||
if(!uri.Contains("://"))
|
||||
uri = browser.GetUriFunction(addressNode.Text).Trim();
|
||||
if(uri != null && uri.Contains("."))
|
||||
{
|
||||
uri = string.Concat("http://", uri);
|
||||
}
|
||||
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
|
||||
{
|
||||
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
|
||||
if(parts.Length > 1)
|
||||
if(!uri.Contains("://") && !uri.Contains(" "))
|
||||
{
|
||||
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
||||
if(urlPart != null)
|
||||
uri = string.Concat("http://", uri);
|
||||
}
|
||||
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
|
||||
{
|
||||
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
|
||||
if(parts.Length > 1)
|
||||
{
|
||||
uri = urlPart.Trim();
|
||||
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
||||
if(urlPart != null)
|
||||
{
|
||||
uri = urlPart.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,11 +200,16 @@ namespace Bit.Android
|
||||
|
||||
private static bool EditText(AccessibilityNodeInfo n)
|
||||
{
|
||||
return n.ClassName != null && n.ClassName.Contains("EditText");
|
||||
return n?.ClassName?.Contains("EditText") ?? false;
|
||||
}
|
||||
|
||||
private void NotifyToAutofill(string uri)
|
||||
private void NotifyToAutofill(string uri, NotificationManager notificationManager)
|
||||
{
|
||||
if(notificationManager == null || string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var intent = new Intent(this, typeof(AutofillActivity));
|
||||
intent.PutExtra("uri", uri);
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
@@ -225,16 +230,17 @@ namespace Bit.Android
|
||||
Resource.Color.primary));
|
||||
}
|
||||
|
||||
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||
|
||||
builder.Dispose();
|
||||
}
|
||||
|
||||
private void FillCredentials(AccessibilityNodeInfo usernameNode, IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||
{
|
||||
FillEditText(usernameNode, AutofillActivity.LastCredentials.Username);
|
||||
FillEditText(usernameNode, AutofillActivity.LastCredentials?.Username);
|
||||
foreach(var n in passwordNodes)
|
||||
{
|
||||
FillEditText(n, AutofillActivity.LastCredentials.Password);
|
||||
FillEditText(n, AutofillActivity.LastCredentials?.Password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,22 +256,63 @@ namespace Bit.Android
|
||||
editTextNode.PerformAction(global::Android.Views.Accessibility.Action.SetText, bundle);
|
||||
}
|
||||
|
||||
private IEnumerable<AccessibilityNodeInfo> GetWindowNodes(AccessibilityNodeInfo n,
|
||||
AccessibilityEvent e, Func<AccessibilityNodeInfo, bool> p)
|
||||
private NodeList GetWindowNodes(AccessibilityNodeInfo n, AccessibilityEvent e,
|
||||
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null)
|
||||
{
|
||||
if(nodes == null)
|
||||
{
|
||||
nodes = new NodeList();
|
||||
}
|
||||
|
||||
if(n != null)
|
||||
{
|
||||
if(n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && p(n))
|
||||
var dispose = disposeIfUnused;
|
||||
if(n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && condition(n))
|
||||
{
|
||||
yield return n;
|
||||
dispose = false;
|
||||
nodes.Add(n);
|
||||
}
|
||||
|
||||
for(int i = 0; i < n.ChildCount; i++)
|
||||
for(var i = 0; i < n.ChildCount; i++)
|
||||
{
|
||||
foreach(var node in GetWindowNodes(n.GetChild(i), e, p))
|
||||
{
|
||||
yield return node;
|
||||
}
|
||||
GetWindowNodes(n.GetChild(i), e, condition, true, nodes);
|
||||
}
|
||||
|
||||
if(dispose)
|
||||
{
|
||||
n.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public class Browser
|
||||
{
|
||||
public Browser(string packageName, string uriViewId)
|
||||
{
|
||||
PackageName = packageName;
|
||||
UriViewId = uriViewId;
|
||||
}
|
||||
|
||||
public Browser(string packageName, string uriViewId, Func<string, string> getUriFunction)
|
||||
: this(packageName, uriViewId)
|
||||
{
|
||||
GetUriFunction = getUriFunction;
|
||||
}
|
||||
|
||||
public string PackageName { get; set; }
|
||||
public string UriViewId { get; set; }
|
||||
public Func<string, string> GetUriFunction { get; set; } = (s) => s;
|
||||
}
|
||||
|
||||
public class NodeList : List<AccessibilityNodeInfo>, IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var item in this)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,15 @@ namespace Bit.Android.Controls
|
||||
{
|
||||
private bool _isPassword;
|
||||
private bool _toggledPassword;
|
||||
private bool _isDisposed;
|
||||
private ExtendedEntry _view;
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
var view = (ExtendedEntry)Element;
|
||||
_isPassword = view.IsPassword;
|
||||
_view = (ExtendedEntry)Element;
|
||||
_isPassword = _view.IsPassword;
|
||||
|
||||
if(Control != null)
|
||||
{
|
||||
@@ -36,69 +38,74 @@ namespace Bit.Android.Controls
|
||||
}
|
||||
}
|
||||
|
||||
SetBorder(view);
|
||||
SetMaxLength(view);
|
||||
SetReturnType(view);
|
||||
SetBorder(_view);
|
||||
SetMaxLength(_view);
|
||||
SetReturnType(_view);
|
||||
|
||||
// Editor Action is called when the return button is pressed
|
||||
Control.EditorAction += (object sender, TextView.EditorActionEventArgs args) =>
|
||||
{
|
||||
if(view.ReturnType != ReturnType.Next)
|
||||
{
|
||||
view.Unfocus();
|
||||
}
|
||||
Control.EditorAction += Control_EditorAction;
|
||||
|
||||
// Call all the methods attached to base_entry event handler Completed
|
||||
view.InvokeCompleted();
|
||||
};
|
||||
|
||||
if(view.DisableAutocapitalize)
|
||||
if(_view.DisableAutocapitalize)
|
||||
{
|
||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextVariationEmailAddress);
|
||||
}
|
||||
|
||||
if(view.Autocorrect.HasValue)
|
||||
if(_view.Autocorrect.HasValue)
|
||||
{
|
||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextFlagNoSuggestions);
|
||||
}
|
||||
|
||||
view.ToggleIsPassword += (object sender, EventArgs args) =>
|
||||
{
|
||||
var cursorStart = Control.SelectionStart;
|
||||
var cursorEnd = Control.SelectionEnd;
|
||||
_view.ToggleIsPassword += ToggleIsPassword;
|
||||
|
||||
Control.TransformationMethod = _isPassword ? null : new PasswordTransformationMethod();
|
||||
|
||||
// set focus
|
||||
Control.RequestFocus();
|
||||
|
||||
if(_toggledPassword)
|
||||
{
|
||||
// restore cursor position
|
||||
Control.SetSelection(cursorStart, cursorEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set cursor to end
|
||||
Control.SetSelection(Control.Text.Length);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
_isPassword = view.IsPasswordFromToggled = !_isPassword;
|
||||
_toggledPassword = true;
|
||||
};
|
||||
|
||||
if(view.FontFamily == "monospace")
|
||||
if(_view.FontFamily == "monospace")
|
||||
{
|
||||
Control.Typeface = Typeface.Monospace;
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleIsPassword(object sender, EventArgs e)
|
||||
{
|
||||
var cursorStart = Control.SelectionStart;
|
||||
var cursorEnd = Control.SelectionEnd;
|
||||
|
||||
Control.TransformationMethod = _isPassword ? null : new PasswordTransformationMethod();
|
||||
|
||||
// set focus
|
||||
Control.RequestFocus();
|
||||
|
||||
if(_toggledPassword)
|
||||
{
|
||||
// restore cursor position
|
||||
Control.SetSelection(cursorStart, cursorEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set cursor to end
|
||||
Control.SetSelection(Control.Text.Length);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
_isPassword = _view.IsPasswordFromToggled = !_isPassword;
|
||||
_toggledPassword = true;
|
||||
}
|
||||
|
||||
private void Control_EditorAction(object sender, TextView.EditorActionEventArgs e)
|
||||
{
|
||||
if(_view.ReturnType != ReturnType.Next)
|
||||
{
|
||||
_view.Unfocus();
|
||||
}
|
||||
|
||||
// Call all the methods attached to base_entry event handler Completed
|
||||
_view.InvokeCompleted();
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var view = (ExtendedEntry)Element;
|
||||
@@ -124,6 +131,23 @@ namespace Bit.Android.Controls
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if(_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
if(disposing && Control != null)
|
||||
{
|
||||
_view.ToggleIsPassword -= ToggleIsPassword;
|
||||
Control.EditorAction -= Control_EditorAction;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void SetReturnType(ExtendedEntry view)
|
||||
{
|
||||
if(view.ReturnType.HasValue)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
|
||||
@@ -1,774 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Android.Runtime;
|
||||
using Java.IO;
|
||||
using Java.Net;
|
||||
using Java.Security;
|
||||
using Java.Security.Cert;
|
||||
using Javax.Net.Ssl;
|
||||
|
||||
namespace Xamarin.Android.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom implementation of <see cref="System.Net.Http.HttpClientHandler"/> which internally uses <see cref="Java.Net.HttpURLConnection"/>
|
||||
/// (or its HTTPS incarnation) to send HTTP requests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Instance of this class is used to configure <see cref="System.Net.Http.HttpClient"/> instance
|
||||
/// in the following way:
|
||||
///
|
||||
/// <example>
|
||||
/// var handler = new AndroidClientHandler {
|
||||
/// UseCookies = true,
|
||||
/// AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
|
||||
/// };
|
||||
///
|
||||
/// var httpClient = new HttpClient (handler);
|
||||
/// var response = httpClient.GetAsync ("http://example.com")?.Result as AndroidHttpResponseMessage;
|
||||
/// </example></para>
|
||||
/// <para>
|
||||
/// The class supports pre-authentication of requests albeit in a slightly "manual" way. Namely, whenever a request to a server requiring authentication
|
||||
/// is made and no authentication credentials are provided in the <see cref="PreAuthenticationData"/> property (which is usually the case on the first
|
||||
/// request), the <see cref="RequestNeedsAuthorization"/> property will return <c>true</c> and the <see cref="RequestedAuthentication"/> property will
|
||||
/// contain all the authentication information gathered from the server. The application must then fill in the blanks (i.e. the credentials) and re-send
|
||||
/// the request configured to perform pre-authentication. The reason for this manual process is that the underlying Java HTTP client API supports only a
|
||||
/// single, VM-wide, authentication handler which cannot be configured to handle credentials for several requests. AndroidClientHandler, therefore, implements
|
||||
/// the authentication in managed .NET code. Message handler supports both Basic and Digest authentication. If an authentication scheme that's not supported
|
||||
/// by AndroidClientHandler is requested by the server, the application can provide its own authentication module (<see cref="AuthenticationData"/>,
|
||||
/// <see cref="PreAuthenticationData"/>) to handle the protocol authorization.</para>
|
||||
/// <para>AndroidClientHandler also supports requests to servers with "invalid" (e.g. self-signed) SSL certificates. Since this process is a bit convoluted using
|
||||
/// the Java APIs, AndroidClientHandler defines two ways to handle the situation. First, easier, is to store the necessary certificates (either CA or server certificates)
|
||||
/// in the <see cref="TrustedCerts"/> collection or, after deriving a custom class from AndroidClientHandler, by overriding one or more methods provided for this purpose
|
||||
/// (<see cref="ConfigureTrustManagerFactory"/>, <see cref="ConfigureKeyManagerFactory"/> and <see cref="ConfigureKeyStore"/>). The former method should be sufficient
|
||||
/// for most use cases, the latter allows the application to provide fully customized key store, trust manager and key manager, if needed. Note that the instance of
|
||||
/// AndroidClientHandler configured to accept an "invalid" certificate from the particular server will most likely fail to validate certificates from other servers (even
|
||||
/// if they use a certificate with a fully validated trust chain) unless you store the CA certificates from your Android system in <see cref="TrustedCerts"/> along with
|
||||
/// the self-signed certificate(s).</para>
|
||||
/// </remarks>
|
||||
public class CustomAndroidClientHandler : HttpClientHandler
|
||||
{
|
||||
sealed class RequestRedirectionState
|
||||
{
|
||||
public Uri NewUrl;
|
||||
public int RedirectCounter;
|
||||
public HttpMethod Method;
|
||||
}
|
||||
|
||||
internal const string LOG_APP = "monodroid-net";
|
||||
|
||||
const string GZIP_ENCODING = "gzip";
|
||||
const string DEFLATE_ENCODING = "deflate";
|
||||
const string IDENTITY_ENCODING = "identity";
|
||||
|
||||
static readonly HashSet<string> known_content_headers = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
|
||||
"Allow",
|
||||
"Content-Disposition",
|
||||
"Content-Encoding",
|
||||
"Content-Language",
|
||||
"Content-Length",
|
||||
"Content-Location",
|
||||
"Content-MD5",
|
||||
"Content-Range",
|
||||
"Content-Type",
|
||||
"Expires",
|
||||
"Last-Modified"
|
||||
};
|
||||
|
||||
static readonly List<IAndroidAuthenticationModule> authModules = new List<IAndroidAuthenticationModule> {
|
||||
new AuthModuleBasic (),
|
||||
// COMMENTED OUT: Kyle
|
||||
//new AuthModuleDigest ()
|
||||
};
|
||||
|
||||
bool disposed;
|
||||
|
||||
// Now all hail Java developers! Get this... HttpURLClient defaults to accepting AND
|
||||
// uncompressing the gzip content encoding UNLESS you set the Accept-Encoding header to ANY
|
||||
// value. So if we set it to 'gzip' below we WILL get gzipped stream but HttpURLClient will NOT
|
||||
// uncompress it any longer, doh. And they don't support 'deflate' so we need to handle it ourselves.
|
||||
bool decompress_here;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Gets or sets the pre authentication data for the request. This property must be set by the application
|
||||
/// before the request is made. Generally the value can be taken from <see cref="RequestedAuthentication"/>
|
||||
/// after the initial request, without any authentication data, receives the authorization request from the
|
||||
/// server. The application must then store credentials in instance of <see cref="AuthenticationData"/> and
|
||||
/// assign the instance to this propery before retrying the request.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The property is never set by AndroidClientHandler.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <value>The pre authentication data.</value>
|
||||
public AuthenticationData PreAuthenticationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the website requires authentication, this property will contain data about each scheme supported
|
||||
/// by the server after the response. Note that unauthorized request will return a valid response - you
|
||||
/// need to check the status code and and (re)configure AndroidClientHandler instance accordingly by providing
|
||||
/// both the credentials and the authentication scheme by setting the <see cref="PreAuthenticationData"/>
|
||||
/// property. If AndroidClientHandler is not able to detect the kind of authentication scheme it will store an
|
||||
/// instance of <see cref="AuthenticationData"/> with its <see cref="AuthenticationData.Scheme"/> property
|
||||
/// set to <c>AuthenticationScheme.Unsupported</c> and the application will be responsible for providing an
|
||||
/// instance of <see cref="IAndroidAuthenticationModule"/> which handles this kind of authorization scheme
|
||||
/// (<see cref="AuthenticationData.AuthModule"/>
|
||||
/// </summary>
|
||||
public IList<AuthenticationData> RequestedAuthentication { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Server authentication response indicates that the request to authorize comes from a proxy if this property is <c>true</c>.
|
||||
/// All the instances of <see cref="AuthenticationData"/> stored in the <see cref="RequestedAuthentication"/> property will
|
||||
/// have their <see cref="AuthenticationData.UseProxyAuthentication"/> preset to the same value as this property.
|
||||
/// </summary>
|
||||
public bool ProxyAuthenticationRequested { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If <c>true</c> then the server requested authorization and the application must use information
|
||||
/// found in <see cref="RequestedAuthentication"/> to set the value of <see cref="PreAuthenticationData"/>
|
||||
/// </summary>
|
||||
public bool RequestNeedsAuthorization
|
||||
{
|
||||
get { return RequestedAuthentication?.Count > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// If the request is to the server protected with a self-signed (or otherwise untrusted) SSL certificate, the request will
|
||||
/// fail security chain verification unless the application provides either the CA certificate of the entity which issued the
|
||||
/// server's certificate or, alternatively, provides the server public key. Whichever the case, the certificate(s) must be stored
|
||||
/// in this property in order for AndroidClientHandler to configure the request to accept the server certificate.</para>
|
||||
/// <para>AndroidClientHandler uses a custom <see cref="KeyStore"/> and <see cref="TrustManagerFactory"/> to configure the connection.
|
||||
/// If, however, the application requires finer control over the SSL configuration (e.g. it implements its own TrustManager) then
|
||||
/// it should leave this property empty and instead derive a custom class from AndroidClientHandler and override, as needed, the
|
||||
/// <see cref="ConfigureTrustManagerFactory"/>, <see cref="ConfigureKeyManagerFactory"/> and <see cref="ConfigureKeyStore"/> methods
|
||||
/// instead</para>
|
||||
/// </summary>
|
||||
/// <value>The trusted certs.</value>
|
||||
public IList<Certificate> TrustedCerts { get; set; }
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
disposed = true;
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected void AssertSelf()
|
||||
{
|
||||
if(!disposed)
|
||||
return;
|
||||
throw new ObjectDisposedException(nameof(AndroidClientHandler));
|
||||
}
|
||||
|
||||
string EncodeUrl(Uri url)
|
||||
{
|
||||
if(url == null)
|
||||
return String.Empty;
|
||||
|
||||
if(String.IsNullOrEmpty(url.Query))
|
||||
return Uri.EscapeUriString(url.ToString());
|
||||
|
||||
// UriBuilder takes care of encoding everything properly
|
||||
var bldr = new UriBuilder(url);
|
||||
if(url.IsDefaultPort)
|
||||
bldr.Port = -1; // Avoids adding :80 or :443 to the host name in the result
|
||||
|
||||
// bldr.Uri.ToString () would ruin the good job UriBuilder did
|
||||
return bldr.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates, configures and processes an asynchronous request to the indicated resource.
|
||||
/// </summary>
|
||||
/// <returns>Task in which the request is executed</returns>
|
||||
/// <param name="request">Request provided by <see cref="System.Net.Http.HttpClient"/></param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
AssertSelf();
|
||||
if(request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
|
||||
if(!request.RequestUri.IsAbsoluteUri)
|
||||
throw new ArgumentException("Must represent an absolute URI", "request");
|
||||
|
||||
var redirectState = new RequestRedirectionState
|
||||
{
|
||||
NewUrl = request.RequestUri,
|
||||
RedirectCounter = 0,
|
||||
Method = request.Method
|
||||
};
|
||||
while(true)
|
||||
{
|
||||
URL java_url = new URL(EncodeUrl(redirectState.NewUrl));
|
||||
URLConnection java_connection = java_url.OpenConnection();
|
||||
HttpURLConnection httpConnection = await SetupRequestInternal(request, java_connection);
|
||||
HttpResponseMessage response = await ProcessRequest(request, java_url, httpConnection, cancellationToken, redirectState);
|
||||
if(response != null)
|
||||
return response;
|
||||
|
||||
if(redirectState.NewUrl == null)
|
||||
throw new InvalidOperationException("Request redirected but no new URI specified");
|
||||
request.Method = redirectState.Method;
|
||||
}
|
||||
}
|
||||
|
||||
Task<HttpResponseMessage> ProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
httpConnection.InstanceFollowRedirects = false; // We handle it ourselves
|
||||
RequestedAuthentication = null;
|
||||
ProxyAuthenticationRequested = false;
|
||||
|
||||
return DoProcessRequest(request, javaUrl, httpConnection, cancellationToken, redirectState);
|
||||
}
|
||||
|
||||
async Task<HttpResponseMessage> DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState)
|
||||
{
|
||||
if(cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAny(
|
||||
httpConnection.ConnectAsync(),
|
||||
Task.Run(() => { cancellationToken.WaitHandle.WaitOne(); }))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch(Java.Net.ConnectException ex)
|
||||
{
|
||||
// Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler
|
||||
throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null);
|
||||
}
|
||||
|
||||
if(cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
httpConnection.Disconnect();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
cancellationToken.Register(httpConnection.Disconnect);
|
||||
|
||||
if(httpConnection.DoOutput)
|
||||
{
|
||||
using(var stream = await request.Content.ReadAsStreamAsync())
|
||||
{
|
||||
await stream.CopyToAsync(httpConnection.OutputStream, 4096, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if(cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
httpConnection.Disconnect();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
var statusCode = await Task.Run(() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait(false);
|
||||
var connectionUri = new Uri(httpConnection.URL.ToString());
|
||||
|
||||
// If the request was redirected we need to put the new URL in the request
|
||||
request.RequestUri = connectionUri;
|
||||
var ret = new AndroidHttpResponseMessage(javaUrl, httpConnection)
|
||||
{
|
||||
RequestMessage = request,
|
||||
ReasonPhrase = httpConnection.ResponseMessage,
|
||||
StatusCode = statusCode,
|
||||
};
|
||||
|
||||
bool disposeRet;
|
||||
if(HandleRedirect(statusCode, httpConnection, redirectState, out disposeRet))
|
||||
{
|
||||
if(disposeRet)
|
||||
{
|
||||
ret.Dispose();
|
||||
ret = null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch(statusCode)
|
||||
{
|
||||
case HttpStatusCode.Unauthorized:
|
||||
case HttpStatusCode.ProxyAuthenticationRequired:
|
||||
// We don't resend the request since that would require new set of credentials if the
|
||||
// ones provided in Credentials are invalid (or null) and that, in turn, may require asking the
|
||||
// user which is not something that should be taken care of by us and in this
|
||||
// context. The application should be responsible for this.
|
||||
// HttpClientHandler throws an exception in this instance, but I think it's not a good
|
||||
// idea. We'll return the response message with all the information required by the
|
||||
// application to fill in the blanks and provide the requested credentials instead.
|
||||
//
|
||||
// We return the body of the response too, but the Java client will throw
|
||||
// a FileNotFound exception if we attempt to access the input stream.
|
||||
// Instead we try to read the error stream and return an default message if the error stream isn't readable.
|
||||
ret.Content = GetErrorContent(httpConnection, new StringContent("Unauthorized", Encoding.ASCII));
|
||||
CopyHeaders(httpConnection, ret);
|
||||
|
||||
if(ret.Headers.WwwAuthenticate != null)
|
||||
{
|
||||
ProxyAuthenticationRequested = false;
|
||||
CollectAuthInfo(ret.Headers.WwwAuthenticate);
|
||||
}
|
||||
else if(ret.Headers.ProxyAuthenticate != null)
|
||||
{
|
||||
ProxyAuthenticationRequested = true;
|
||||
CollectAuthInfo(ret.Headers.ProxyAuthenticate);
|
||||
}
|
||||
|
||||
// COMMENTED OUT: Kyle
|
||||
//ret.RequestedAuthentication = RequestedAuthentication;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if(!IsErrorStatusCode(statusCode))
|
||||
{
|
||||
ret.Content = GetContent(httpConnection, httpConnection.InputStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream.
|
||||
// Instead we try to read the error stream and return an empty string if the error stream isn't readable.
|
||||
ret.Content = GetErrorContent(httpConnection, new StringContent(String.Empty, Encoding.ASCII));
|
||||
}
|
||||
|
||||
CopyHeaders(httpConnection, ret);
|
||||
|
||||
IEnumerable<string> cookieHeaderValue;
|
||||
if(!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CookieContainer.SetCookies(connectionUri, String.Join(",", cookieHeaderValue));
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// We don't want to terminate the response because of a bad cookie, hence just reporting
|
||||
// the issue. We might consider adding a virtual method to let the user handle the
|
||||
// issue, but not sure if it's really needed. Set-Cookie header will be part of the
|
||||
// header collection so the user can always examine it if they spot an error.
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
HttpContent GetErrorContent(HttpURLConnection httpConnection, HttpContent fallbackContent)
|
||||
{
|
||||
var contentStream = httpConnection.ErrorStream;
|
||||
|
||||
if(contentStream != null)
|
||||
{
|
||||
return GetContent(httpConnection, contentStream);
|
||||
}
|
||||
|
||||
return fallbackContent;
|
||||
}
|
||||
|
||||
HttpContent GetContent(URLConnection httpConnection, Stream contentStream)
|
||||
{
|
||||
Stream inputStream = new BufferedStream(contentStream);
|
||||
if(decompress_here)
|
||||
{
|
||||
string[] encodings = httpConnection.ContentEncoding?.Split(',');
|
||||
if(encodings != null)
|
||||
{
|
||||
if(encodings.Contains(GZIP_ENCODING, StringComparer.OrdinalIgnoreCase))
|
||||
inputStream = new GZipStream(inputStream, CompressionMode.Decompress);
|
||||
else if(encodings.Contains(DEFLATE_ENCODING, StringComparer.OrdinalIgnoreCase))
|
||||
inputStream = new DeflateStream(inputStream, CompressionMode.Decompress);
|
||||
}
|
||||
}
|
||||
return new StreamContent(inputStream);
|
||||
}
|
||||
|
||||
bool HandleRedirect(HttpStatusCode redirectCode, HttpURLConnection httpConnection, RequestRedirectionState redirectState, out bool disposeRet)
|
||||
{
|
||||
if(!AllowAutoRedirect)
|
||||
{
|
||||
disposeRet = false;
|
||||
return true; // We shouldn't follow and there's no data to fetch, just return
|
||||
}
|
||||
disposeRet = true;
|
||||
|
||||
redirectState.NewUrl = null;
|
||||
switch(redirectCode)
|
||||
{
|
||||
case HttpStatusCode.MultipleChoices: // 300
|
||||
break;
|
||||
|
||||
case HttpStatusCode.Moved: // 301
|
||||
case HttpStatusCode.Redirect: // 302
|
||||
case HttpStatusCode.SeeOther: // 303
|
||||
redirectState.Method = HttpMethod.Get;
|
||||
break;
|
||||
|
||||
case HttpStatusCode.TemporaryRedirect: // 307
|
||||
break;
|
||||
|
||||
default:
|
||||
if((int)redirectCode >= 300 && (int)redirectCode < 400)
|
||||
throw new InvalidOperationException($"HTTP Redirection status code {redirectCode} ({(int)redirectCode}) not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
IDictionary<string, IList<string>> headers = httpConnection.HeaderFields;
|
||||
IList<string> locationHeader;
|
||||
if(!headers.TryGetValue("Location", out locationHeader) || locationHeader == null || locationHeader.Count == 0)
|
||||
throw new InvalidOperationException($"HTTP connection redirected with code {redirectCode} ({(int)redirectCode}) but no Location header found in response");
|
||||
|
||||
|
||||
redirectState.RedirectCounter++;
|
||||
if(redirectState.RedirectCounter >= MaxAutomaticRedirections)
|
||||
throw new WebException($"Maximum automatic redirections exceeded (allowed {MaxAutomaticRedirections}, redirected {redirectState.RedirectCounter} times)");
|
||||
|
||||
Uri location = new Uri(locationHeader[0], UriKind.Absolute);
|
||||
redirectState.NewUrl = location;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsErrorStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
return (int)statusCode >= 400 && (int)statusCode <= 599;
|
||||
}
|
||||
|
||||
void CollectAuthInfo(HttpHeaderValueCollection<AuthenticationHeaderValue> headers)
|
||||
{
|
||||
var authData = new List<AuthenticationData>(headers.Count);
|
||||
|
||||
foreach(AuthenticationHeaderValue ahv in headers)
|
||||
{
|
||||
var data = new AuthenticationData
|
||||
{
|
||||
Scheme = GetAuthScheme(ahv.Scheme),
|
||||
// COMMENTED OUT: Kyle
|
||||
//Challenge = $"{ahv.Scheme} {ahv.Parameter}",
|
||||
UseProxyAuthentication = ProxyAuthenticationRequested
|
||||
};
|
||||
authData.Add(data);
|
||||
}
|
||||
|
||||
RequestedAuthentication = authData.AsReadOnly();
|
||||
}
|
||||
|
||||
AuthenticationScheme GetAuthScheme(string scheme)
|
||||
{
|
||||
if(String.Compare("basic", scheme, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
return AuthenticationScheme.Basic;
|
||||
if(String.Compare("digest", scheme, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
return AuthenticationScheme.Digest;
|
||||
|
||||
return AuthenticationScheme.Unsupported;
|
||||
}
|
||||
|
||||
void CopyHeaders(HttpURLConnection httpConnection, HttpResponseMessage response)
|
||||
{
|
||||
IDictionary<string, IList<string>> headers = httpConnection.HeaderFields;
|
||||
foreach(string key in headers.Keys)
|
||||
{
|
||||
if(key == null) // First header entry has null key, it corresponds to the response message
|
||||
continue;
|
||||
|
||||
HttpHeaders item_headers;
|
||||
string kind;
|
||||
if(known_content_headers.Contains(key))
|
||||
{
|
||||
kind = "content";
|
||||
item_headers = response.Content.Headers;
|
||||
}
|
||||
else
|
||||
{
|
||||
kind = "response";
|
||||
item_headers = response.Headers;
|
||||
}
|
||||
item_headers.TryAddWithoutValidation(key, headers[key]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the <see cref="HttpURLConnection"/> before the request is sent. This method is meant to be overriden
|
||||
/// by applications which need to perform some extra configuration steps on the connection. It is called with all
|
||||
/// the request headers set, pre-authentication performed (if applicable) but before the request body is set
|
||||
/// (e.g. for POST requests). The default implementation in AndroidClientHandler does nothing.
|
||||
/// </summary>
|
||||
/// <param name="request">Request data</param>
|
||||
/// <param name="conn">Pre-configured connection instance</param>
|
||||
protected virtual Task SetupRequest(HttpRequestMessage request, HttpURLConnection conn)
|
||||
{
|
||||
return Task.Factory.StartNew(AssertSelf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the key store. The <paramref name="keyStore"/> parameter is set to instance of <see cref="KeyStore"/>
|
||||
/// created using the <see cref="KeyStore.DefaultType"/> type and with populated with certificates provided in the <see cref="TrustedCerts"/>
|
||||
/// property. AndroidClientHandler implementation simply returns the instance passed in the <paramref name="keyStore"/> parameter
|
||||
/// </summary>
|
||||
/// <returns>The key store.</returns>
|
||||
/// <param name="keyStore">Key store to configure.</param>
|
||||
protected virtual KeyStore ConfigureKeyStore(KeyStore keyStore)
|
||||
{
|
||||
AssertSelf();
|
||||
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and configure an instance of <see cref="KeyManagerFactory"/>. The <paramref name="keyStore"/> parameter is set to the
|
||||
/// return value of the <see cref="ConfigureKeyStore"/> method, so it might be null if the application overrode the method and provided
|
||||
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> here since
|
||||
/// KeyManagerFactory is not required for the custom SSL configuration, but it might be used by the application to implement a more advanced
|
||||
/// mechanism of key management.
|
||||
/// </summary>
|
||||
/// <returns>The key manager factory or <c>null</c>.</returns>
|
||||
/// <param name="keyStore">Key store.</param>
|
||||
protected virtual KeyManagerFactory ConfigureKeyManagerFactory(KeyStore keyStore)
|
||||
{
|
||||
AssertSelf();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and configure an instance of <see cref="TrustManagerFactory"/>. The <paramref name="keyStore"/> parameter is set to the
|
||||
/// return value of the <see cref="ConfigureKeyStore"/> method, so it might be null if the application overrode the method and provided
|
||||
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> from this
|
||||
/// method in which case AndroidClientHandler will create its own instance of the trust manager factory provided that the <see cref="TrustCerts"/>
|
||||
/// list contains at least one valid certificate. If there are no valid certificates and this method returns <c>null</c>, no custom
|
||||
/// trust manager will be created since that would make all the HTTPS requests fail.
|
||||
/// </summary>
|
||||
/// <returns>The trust manager factory.</returns>
|
||||
/// <param name="keyStore">Key store.</param>
|
||||
protected virtual TrustManagerFactory ConfigureTrustManagerFactory(KeyStore keyStore)
|
||||
{
|
||||
AssertSelf();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void AppendEncoding(string encoding, ref List<string> list)
|
||||
{
|
||||
if(list == null)
|
||||
list = new List<string>();
|
||||
if(list.Contains(encoding))
|
||||
return;
|
||||
list.Add(encoding);
|
||||
}
|
||||
|
||||
async Task<HttpURLConnection> SetupRequestInternal(HttpRequestMessage request, URLConnection conn)
|
||||
{
|
||||
if(conn == null)
|
||||
throw new ArgumentNullException(nameof(conn));
|
||||
var httpConnection = conn.JavaCast<HttpURLConnection>();
|
||||
if(httpConnection == null)
|
||||
throw new InvalidOperationException($"Unsupported URL scheme {conn.URL.Protocol}");
|
||||
|
||||
httpConnection.RequestMethod = request.Method.ToString();
|
||||
|
||||
// SSL context must be set up as soon as possible, before adding any content or
|
||||
// headers. Otherwise Java won't use the socket factory
|
||||
SetupSSL(httpConnection as HttpsURLConnection);
|
||||
if(request.Content != null)
|
||||
AddHeaders(httpConnection, request.Content.Headers);
|
||||
AddHeaders(httpConnection, request.Headers);
|
||||
|
||||
List<string> accept_encoding = null;
|
||||
|
||||
decompress_here = false;
|
||||
if((AutomaticDecompression & DecompressionMethods.GZip) != 0)
|
||||
{
|
||||
AppendEncoding(GZIP_ENCODING, ref accept_encoding);
|
||||
decompress_here = true;
|
||||
}
|
||||
|
||||
if((AutomaticDecompression & DecompressionMethods.Deflate) != 0)
|
||||
{
|
||||
AppendEncoding(DEFLATE_ENCODING, ref accept_encoding);
|
||||
decompress_here = true;
|
||||
}
|
||||
|
||||
if(AutomaticDecompression == DecompressionMethods.None)
|
||||
{
|
||||
accept_encoding?.Clear();
|
||||
AppendEncoding(IDENTITY_ENCODING, ref accept_encoding); // Turns off compression for the Java client
|
||||
}
|
||||
|
||||
if(accept_encoding?.Count > 0)
|
||||
httpConnection.SetRequestProperty("Accept-Encoding", String.Join(",", accept_encoding));
|
||||
|
||||
if(UseCookies && CookieContainer != null)
|
||||
{
|
||||
string cookieHeaderValue = CookieContainer.GetCookieHeader(request.RequestUri);
|
||||
if(!String.IsNullOrEmpty(cookieHeaderValue))
|
||||
httpConnection.SetRequestProperty("Cookie", cookieHeaderValue);
|
||||
}
|
||||
|
||||
HandlePreAuthentication(httpConnection);
|
||||
await SetupRequest(request, httpConnection);
|
||||
SetupRequestBody(httpConnection, request);
|
||||
|
||||
return httpConnection;
|
||||
}
|
||||
|
||||
void SetupSSL(HttpsURLConnection httpsConnection)
|
||||
{
|
||||
if(httpsConnection == null)
|
||||
return;
|
||||
|
||||
KeyStore keyStore = KeyStore.GetInstance(KeyStore.DefaultType);
|
||||
keyStore.Load(null, null);
|
||||
bool gotCerts = TrustedCerts?.Count > 0;
|
||||
if(gotCerts)
|
||||
{
|
||||
for(int i = 0; i < TrustedCerts.Count; i++)
|
||||
{
|
||||
Certificate cert = TrustedCerts[i];
|
||||
if(cert == null)
|
||||
continue;
|
||||
keyStore.SetCertificateEntry($"ca{i}", cert);
|
||||
}
|
||||
}
|
||||
keyStore = ConfigureKeyStore(keyStore);
|
||||
KeyManagerFactory kmf = ConfigureKeyManagerFactory(keyStore);
|
||||
TrustManagerFactory tmf = ConfigureTrustManagerFactory(keyStore);
|
||||
|
||||
if(tmf == null)
|
||||
{
|
||||
// If there are no certs and no trust manager factory, we can't use a custom manager
|
||||
// because it will cause all the HTTPS requests to fail because of unverified trust
|
||||
// chain
|
||||
if(!gotCerts)
|
||||
return;
|
||||
|
||||
tmf = TrustManagerFactory.GetInstance(TrustManagerFactory.DefaultAlgorithm);
|
||||
tmf.Init(keyStore);
|
||||
}
|
||||
|
||||
SSLContext context = SSLContext.GetInstance("TLS");
|
||||
context.Init(kmf?.GetKeyManagers(), tmf.GetTrustManagers(), null);
|
||||
httpsConnection.SSLSocketFactory = context.SocketFactory;
|
||||
}
|
||||
|
||||
void HandlePreAuthentication(HttpURLConnection httpConnection)
|
||||
{
|
||||
AuthenticationData data = PreAuthenticationData;
|
||||
if(!PreAuthenticate || data == null)
|
||||
return;
|
||||
|
||||
ICredentials creds = data.UseProxyAuthentication ? Proxy?.Credentials : Credentials;
|
||||
if(creds == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IAndroidAuthenticationModule auth = data.Scheme == AuthenticationScheme.Unsupported ? data.AuthModule : authModules.Find(m => m?.Scheme == data.Scheme);
|
||||
if(auth == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Authorization authorization = auth.Authenticate(data.Challenge, httpConnection, creds);
|
||||
if(authorization == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection.SetRequestProperty(data.UseProxyAuthentication ? "Proxy-Authorization" : "Authorization", authorization.Message);
|
||||
}
|
||||
|
||||
void AddHeaders(HttpURLConnection conn, HttpHeaders headers)
|
||||
{
|
||||
if(headers == null)
|
||||
return;
|
||||
|
||||
foreach(KeyValuePair<string, IEnumerable<string>> header in headers)
|
||||
{
|
||||
conn.SetRequestProperty(header.Key, header.Value != null ? String.Join(",", header.Value) : String.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
void SetupRequestBody(HttpURLConnection httpConnection, HttpRequestMessage request)
|
||||
{
|
||||
if(request.Content == null)
|
||||
{
|
||||
// Pilfered from System.Net.Http.HttpClientHandler:SendAync
|
||||
if(HttpMethod.Post.Equals(request.Method) || HttpMethod.Put.Equals(request.Method) || HttpMethod.Delete.Equals(request.Method))
|
||||
{
|
||||
// Explicitly set this to make sure we're sending a "Content-Length: 0" header.
|
||||
// This fixes the issue that's been reported on the forums:
|
||||
// http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
|
||||
httpConnection.SetRequestProperty("Content-Length", "0");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection.DoOutput = true;
|
||||
long? contentLength = request.Content.Headers.ContentLength;
|
||||
if(contentLength != null)
|
||||
httpConnection.SetFixedLengthStreamingMode((int)contentLength);
|
||||
else
|
||||
httpConnection.SetChunkedStreamingMode(0);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class AuthModuleBasic : IAndroidAuthenticationModule
|
||||
{
|
||||
public AuthenticationScheme Scheme { get; } = AuthenticationScheme.Basic;
|
||||
public string AuthenticationType { get; } = "Basic";
|
||||
public bool CanPreAuthenticate { get; } = true;
|
||||
|
||||
public Authorization Authenticate(string challenge, HttpURLConnection request, ICredentials credentials)
|
||||
{
|
||||
string header = challenge?.Trim();
|
||||
if(credentials == null || String.IsNullOrEmpty(header))
|
||||
return null;
|
||||
|
||||
if(header.IndexOf("basic", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
return null;
|
||||
|
||||
return InternalAuthenticate(request, credentials);
|
||||
}
|
||||
|
||||
public Authorization PreAuthenticate(HttpURLConnection request, ICredentials credentials)
|
||||
{
|
||||
return InternalAuthenticate(request, credentials);
|
||||
}
|
||||
|
||||
Authorization InternalAuthenticate(HttpURLConnection request, ICredentials credentials)
|
||||
{
|
||||
if(request == null || credentials == null)
|
||||
return null;
|
||||
|
||||
NetworkCredential cred = credentials.GetCredential(new Uri(request.URL.ToString()), AuthenticationType.ToLowerInvariant());
|
||||
if(cred == null)
|
||||
return null;
|
||||
|
||||
if(String.IsNullOrEmpty(cred.UserName))
|
||||
return null;
|
||||
|
||||
string domain = cred.Domain?.Trim();
|
||||
string response = String.Empty;
|
||||
|
||||
// If domain is set, MS sends "domain\user:password".
|
||||
if(!String.IsNullOrEmpty(domain))
|
||||
response = domain + "\\";
|
||||
response += cred.UserName + ":" + cred.Password;
|
||||
|
||||
return new Authorization($"{AuthenticationType} {Convert.ToBase64String(Encoding.ASCII.GetBytes(response))}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
XA_HTTP_CLIENT_HANDLER_TYPE=Xamarin.Android.Net.AndroidClientHandler
|
||||
@@ -28,22 +28,7 @@ namespace Bit.Android
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
string uri = null;
|
||||
if(!Intent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory) && Intent.HasExtra("uri") && Intent.HasExtra("ts"))
|
||||
{
|
||||
var tsDiff = Java.Lang.JavaSystem.CurrentTimeMillis() - Intent.GetLongExtra("ts", 0);
|
||||
if(tsDiff < 5000)
|
||||
{
|
||||
uri = Intent.GetStringExtra("uri");
|
||||
}
|
||||
|
||||
// Attempt to clear intent for future
|
||||
Intent.ReplaceExtras(new Bundle());
|
||||
Intent.SetAction(string.Empty);
|
||||
Intent.SetData(null);
|
||||
Intent.SetFlags(0);
|
||||
}
|
||||
|
||||
var uri = Intent.GetStringExtra("uri");
|
||||
if(!Resolver.IsSet)
|
||||
{
|
||||
MainApplication.SetIoc(Application);
|
||||
@@ -88,7 +73,8 @@ namespace Bit.Android
|
||||
Resolver.Resolve<ILockService>(),
|
||||
Resolver.Resolve<IGoogleAnalyticsService>(),
|
||||
Resolver.Resolve<ILocalizeService>(),
|
||||
Resolver.Resolve<IAppInfoService>()));
|
||||
Resolver.Resolve<IAppInfoService>(),
|
||||
Resolver.Resolve<IAppSettingsService>()));
|
||||
|
||||
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, "RateApp", (sender) =>
|
||||
{
|
||||
@@ -134,7 +120,7 @@ namespace Bit.Android
|
||||
{
|
||||
Parent.SetResult(Result.Ok, data);
|
||||
}
|
||||
|
||||
|
||||
Finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using XLabs.Ioc.Unity;
|
||||
using System.Threading.Tasks;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Xamarin.Android.Net;
|
||||
using FFImageLoading.Forms.Droid;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
@@ -38,9 +39,6 @@ namespace Bit.Android
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership transer)
|
||||
: base(handle, transer)
|
||||
{
|
||||
// NOTE: This is just here to stop the linker from removing AndroidClientHandler references
|
||||
var handler = new AndroidClientHandler();
|
||||
|
||||
if(!Resolver.IsSet)
|
||||
{
|
||||
SetIoc(this);
|
||||
@@ -212,6 +210,8 @@ namespace Bit.Android
|
||||
.RegisterType<IHttpService, HttpService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ITokenService, TokenService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ISettingsService, SettingsService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IMemoryService, MemoryService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IAppSettingsService, AppSettingsService>(new ContainerControlledLifetimeManager())
|
||||
// Repositories
|
||||
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
|
||||
@@ -231,6 +231,7 @@ namespace Bit.Android
|
||||
|
||||
CrossPushNotification.Initialize(container.Resolve<IPushNotificationListener>(), "962181367620");
|
||||
container.RegisterInstance(CrossPushNotification.Current, new ContainerControlledLifetimeManager());
|
||||
CachedImageRenderer.Init();
|
||||
|
||||
Resolver.SetResolver(new UnityResolver(container));
|
||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.3.0" android:installLocation="auto" android:versionCode="101">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.5.1" 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" />
|
||||
|
||||
336
src/Android/Resources/Resource.Designer.cs
generated
@@ -2293,493 +2293,499 @@ namespace Bit.Android
|
||||
public const int accessibility_step2 = 2130837582;
|
||||
|
||||
// aapt resource value: 0x7f02004f
|
||||
public const int close = 2130837583;
|
||||
public const int cloudup = 2130837583;
|
||||
|
||||
// aapt resource value: 0x7f020050
|
||||
public const int cloudup = 2130837584;
|
||||
public const int cogs = 2130837584;
|
||||
|
||||
// aapt resource value: 0x7f020051
|
||||
public const int cogs = 2130837585;
|
||||
public const int cogs_selected = 2130837585;
|
||||
|
||||
// aapt resource value: 0x7f020052
|
||||
public const int cogs_selected = 2130837586;
|
||||
public const int common_full_open_on_phone = 2130837586;
|
||||
|
||||
// aapt resource value: 0x7f020053
|
||||
public const int common_full_open_on_phone = 2130837587;
|
||||
public const int common_google_signin_btn_icon_dark = 2130837587;
|
||||
|
||||
// aapt resource value: 0x7f020054
|
||||
public const int common_google_signin_btn_icon_dark = 2130837588;
|
||||
public const int common_google_signin_btn_icon_dark_disabled = 2130837588;
|
||||
|
||||
// aapt resource value: 0x7f020055
|
||||
public const int common_google_signin_btn_icon_dark_disabled = 2130837589;
|
||||
public const int common_google_signin_btn_icon_dark_focused = 2130837589;
|
||||
|
||||
// aapt resource value: 0x7f020056
|
||||
public const int common_google_signin_btn_icon_dark_focused = 2130837590;
|
||||
public const int common_google_signin_btn_icon_dark_normal = 2130837590;
|
||||
|
||||
// aapt resource value: 0x7f020057
|
||||
public const int common_google_signin_btn_icon_dark_normal = 2130837591;
|
||||
public const int common_google_signin_btn_icon_dark_pressed = 2130837591;
|
||||
|
||||
// aapt resource value: 0x7f020058
|
||||
public const int common_google_signin_btn_icon_dark_pressed = 2130837592;
|
||||
public const int common_google_signin_btn_icon_light = 2130837592;
|
||||
|
||||
// aapt resource value: 0x7f020059
|
||||
public const int common_google_signin_btn_icon_light = 2130837593;
|
||||
public const int common_google_signin_btn_icon_light_disabled = 2130837593;
|
||||
|
||||
// aapt resource value: 0x7f02005a
|
||||
public const int common_google_signin_btn_icon_light_disabled = 2130837594;
|
||||
public const int common_google_signin_btn_icon_light_focused = 2130837594;
|
||||
|
||||
// aapt resource value: 0x7f02005b
|
||||
public const int common_google_signin_btn_icon_light_focused = 2130837595;
|
||||
public const int common_google_signin_btn_icon_light_normal = 2130837595;
|
||||
|
||||
// aapt resource value: 0x7f02005c
|
||||
public const int common_google_signin_btn_icon_light_normal = 2130837596;
|
||||
public const int common_google_signin_btn_icon_light_pressed = 2130837596;
|
||||
|
||||
// aapt resource value: 0x7f02005d
|
||||
public const int common_google_signin_btn_icon_light_pressed = 2130837597;
|
||||
public const int common_google_signin_btn_text_dark = 2130837597;
|
||||
|
||||
// aapt resource value: 0x7f02005e
|
||||
public const int common_google_signin_btn_text_dark = 2130837598;
|
||||
public const int common_google_signin_btn_text_dark_disabled = 2130837598;
|
||||
|
||||
// aapt resource value: 0x7f02005f
|
||||
public const int common_google_signin_btn_text_dark_disabled = 2130837599;
|
||||
public const int common_google_signin_btn_text_dark_focused = 2130837599;
|
||||
|
||||
// aapt resource value: 0x7f020060
|
||||
public const int common_google_signin_btn_text_dark_focused = 2130837600;
|
||||
public const int common_google_signin_btn_text_dark_normal = 2130837600;
|
||||
|
||||
// aapt resource value: 0x7f020061
|
||||
public const int common_google_signin_btn_text_dark_normal = 2130837601;
|
||||
public const int common_google_signin_btn_text_dark_pressed = 2130837601;
|
||||
|
||||
// aapt resource value: 0x7f020062
|
||||
public const int common_google_signin_btn_text_dark_pressed = 2130837602;
|
||||
public const int common_google_signin_btn_text_light = 2130837602;
|
||||
|
||||
// aapt resource value: 0x7f020063
|
||||
public const int common_google_signin_btn_text_light = 2130837603;
|
||||
public const int common_google_signin_btn_text_light_disabled = 2130837603;
|
||||
|
||||
// aapt resource value: 0x7f020064
|
||||
public const int common_google_signin_btn_text_light_disabled = 2130837604;
|
||||
public const int common_google_signin_btn_text_light_focused = 2130837604;
|
||||
|
||||
// aapt resource value: 0x7f020065
|
||||
public const int common_google_signin_btn_text_light_focused = 2130837605;
|
||||
public const int common_google_signin_btn_text_light_normal = 2130837605;
|
||||
|
||||
// aapt resource value: 0x7f020066
|
||||
public const int common_google_signin_btn_text_light_normal = 2130837606;
|
||||
public const int common_google_signin_btn_text_light_pressed = 2130837606;
|
||||
|
||||
// aapt resource value: 0x7f020067
|
||||
public const int common_google_signin_btn_text_light_pressed = 2130837607;
|
||||
public const int common_ic_googleplayservices = 2130837607;
|
||||
|
||||
// aapt resource value: 0x7f020068
|
||||
public const int common_ic_googleplayservices = 2130837608;
|
||||
public const int common_plus_signin_btn_icon_dark = 2130837608;
|
||||
|
||||
// aapt resource value: 0x7f020069
|
||||
public const int common_plus_signin_btn_icon_dark = 2130837609;
|
||||
public const int common_plus_signin_btn_icon_dark_disabled = 2130837609;
|
||||
|
||||
// aapt resource value: 0x7f02006a
|
||||
public const int common_plus_signin_btn_icon_dark_disabled = 2130837610;
|
||||
public const int common_plus_signin_btn_icon_dark_focused = 2130837610;
|
||||
|
||||
// aapt resource value: 0x7f02006b
|
||||
public const int common_plus_signin_btn_icon_dark_focused = 2130837611;
|
||||
public const int common_plus_signin_btn_icon_dark_normal = 2130837611;
|
||||
|
||||
// aapt resource value: 0x7f02006c
|
||||
public const int common_plus_signin_btn_icon_dark_normal = 2130837612;
|
||||
public const int common_plus_signin_btn_icon_dark_pressed = 2130837612;
|
||||
|
||||
// aapt resource value: 0x7f02006d
|
||||
public const int common_plus_signin_btn_icon_dark_pressed = 2130837613;
|
||||
public const int common_plus_signin_btn_icon_light = 2130837613;
|
||||
|
||||
// aapt resource value: 0x7f02006e
|
||||
public const int common_plus_signin_btn_icon_light = 2130837614;
|
||||
public const int common_plus_signin_btn_icon_light_disabled = 2130837614;
|
||||
|
||||
// aapt resource value: 0x7f02006f
|
||||
public const int common_plus_signin_btn_icon_light_disabled = 2130837615;
|
||||
public const int common_plus_signin_btn_icon_light_focused = 2130837615;
|
||||
|
||||
// aapt resource value: 0x7f020070
|
||||
public const int common_plus_signin_btn_icon_light_focused = 2130837616;
|
||||
public const int common_plus_signin_btn_icon_light_normal = 2130837616;
|
||||
|
||||
// aapt resource value: 0x7f020071
|
||||
public const int common_plus_signin_btn_icon_light_normal = 2130837617;
|
||||
public const int common_plus_signin_btn_icon_light_pressed = 2130837617;
|
||||
|
||||
// aapt resource value: 0x7f020072
|
||||
public const int common_plus_signin_btn_icon_light_pressed = 2130837618;
|
||||
public const int common_plus_signin_btn_text_dark = 2130837618;
|
||||
|
||||
// aapt resource value: 0x7f020073
|
||||
public const int common_plus_signin_btn_text_dark = 2130837619;
|
||||
public const int common_plus_signin_btn_text_dark_disabled = 2130837619;
|
||||
|
||||
// aapt resource value: 0x7f020074
|
||||
public const int common_plus_signin_btn_text_dark_disabled = 2130837620;
|
||||
public const int common_plus_signin_btn_text_dark_focused = 2130837620;
|
||||
|
||||
// aapt resource value: 0x7f020075
|
||||
public const int common_plus_signin_btn_text_dark_focused = 2130837621;
|
||||
public const int common_plus_signin_btn_text_dark_normal = 2130837621;
|
||||
|
||||
// aapt resource value: 0x7f020076
|
||||
public const int common_plus_signin_btn_text_dark_normal = 2130837622;
|
||||
public const int common_plus_signin_btn_text_dark_pressed = 2130837622;
|
||||
|
||||
// aapt resource value: 0x7f020077
|
||||
public const int common_plus_signin_btn_text_dark_pressed = 2130837623;
|
||||
public const int common_plus_signin_btn_text_light = 2130837623;
|
||||
|
||||
// aapt resource value: 0x7f020078
|
||||
public const int common_plus_signin_btn_text_light = 2130837624;
|
||||
public const int common_plus_signin_btn_text_light_disabled = 2130837624;
|
||||
|
||||
// aapt resource value: 0x7f020079
|
||||
public const int common_plus_signin_btn_text_light_disabled = 2130837625;
|
||||
public const int common_plus_signin_btn_text_light_focused = 2130837625;
|
||||
|
||||
// aapt resource value: 0x7f02007a
|
||||
public const int common_plus_signin_btn_text_light_focused = 2130837626;
|
||||
public const int common_plus_signin_btn_text_light_normal = 2130837626;
|
||||
|
||||
// aapt resource value: 0x7f02007b
|
||||
public const int common_plus_signin_btn_text_light_normal = 2130837627;
|
||||
public const int common_plus_signin_btn_text_light_pressed = 2130837627;
|
||||
|
||||
// aapt resource value: 0x7f02007c
|
||||
public const int common_plus_signin_btn_text_light_pressed = 2130837628;
|
||||
public const int design_fab_background = 2130837628;
|
||||
|
||||
// aapt resource value: 0x7f02007d
|
||||
public const int design_fab_background = 2130837629;
|
||||
public const int design_snackbar_background = 2130837629;
|
||||
|
||||
// aapt resource value: 0x7f02007e
|
||||
public const int design_snackbar_background = 2130837630;
|
||||
public const int envelope = 2130837630;
|
||||
|
||||
// aapt resource value: 0x7f02007f
|
||||
public const int envelope = 2130837631;
|
||||
public const int eye = 2130837631;
|
||||
|
||||
// aapt resource value: 0x7f020080
|
||||
public const int eye = 2130837632;
|
||||
public const int eye_slash = 2130837632;
|
||||
|
||||
// aapt resource value: 0x7f020081
|
||||
public const int eye_slash = 2130837633;
|
||||
public const int fa_lock = 2130837633;
|
||||
|
||||
// aapt resource value: 0x7f020082
|
||||
public const int fa_lock = 2130837634;
|
||||
public const int fa_lock_selected = 2130837634;
|
||||
|
||||
// aapt resource value: 0x7f020083
|
||||
public const int fa_lock_selected = 2130837635;
|
||||
public const int fingerprint = 2130837635;
|
||||
|
||||
// aapt resource value: 0x7f020084
|
||||
public const int fingerprint = 2130837636;
|
||||
public const int fingerprint_white = 2130837636;
|
||||
|
||||
// aapt resource value: 0x7f020085
|
||||
public const int fingerprint_white = 2130837637;
|
||||
public const int folder = 2130837637;
|
||||
|
||||
// aapt resource value: 0x7f020086
|
||||
public const int folder = 2130837638;
|
||||
public const int globe = 2130837638;
|
||||
|
||||
// aapt resource value: 0x7f020087
|
||||
public const int globe = 2130837639;
|
||||
public const int hockeyapp_btn_background = 2130837639;
|
||||
|
||||
// aapt resource value: 0x7f020088
|
||||
public const int hockeyapp_btn_background = 2130837640;
|
||||
public const int ic_audiotrack = 2130837640;
|
||||
|
||||
// aapt resource value: 0x7f020089
|
||||
public const int ic_audiotrack = 2130837641;
|
||||
public const int ic_audiotrack_light = 2130837641;
|
||||
|
||||
// aapt resource value: 0x7f02008a
|
||||
public const int ic_audiotrack_light = 2130837642;
|
||||
public const int ic_bluetooth_grey = 2130837642;
|
||||
|
||||
// aapt resource value: 0x7f02008b
|
||||
public const int ic_bluetooth_grey = 2130837643;
|
||||
public const int ic_bluetooth_white = 2130837643;
|
||||
|
||||
// aapt resource value: 0x7f02008c
|
||||
public const int ic_bluetooth_white = 2130837644;
|
||||
public const int ic_cast_dark = 2130837644;
|
||||
|
||||
// aapt resource value: 0x7f02008d
|
||||
public const int ic_cast_dark = 2130837645;
|
||||
public const int ic_cast_disabled_light = 2130837645;
|
||||
|
||||
// aapt resource value: 0x7f02008e
|
||||
public const int ic_cast_disabled_light = 2130837646;
|
||||
public const int ic_cast_grey = 2130837646;
|
||||
|
||||
// aapt resource value: 0x7f02008f
|
||||
public const int ic_cast_grey = 2130837647;
|
||||
public const int ic_cast_light = 2130837647;
|
||||
|
||||
// aapt resource value: 0x7f020090
|
||||
public const int ic_cast_light = 2130837648;
|
||||
public const int ic_cast_off_light = 2130837648;
|
||||
|
||||
// aapt resource value: 0x7f020091
|
||||
public const int ic_cast_off_light = 2130837649;
|
||||
public const int ic_cast_on_0_light = 2130837649;
|
||||
|
||||
// aapt resource value: 0x7f020092
|
||||
public const int ic_cast_on_0_light = 2130837650;
|
||||
public const int ic_cast_on_1_light = 2130837650;
|
||||
|
||||
// aapt resource value: 0x7f020093
|
||||
public const int ic_cast_on_1_light = 2130837651;
|
||||
public const int ic_cast_on_2_light = 2130837651;
|
||||
|
||||
// aapt resource value: 0x7f020094
|
||||
public const int ic_cast_on_2_light = 2130837652;
|
||||
public const int ic_cast_on_light = 2130837652;
|
||||
|
||||
// aapt resource value: 0x7f020095
|
||||
public const int ic_cast_on_light = 2130837653;
|
||||
public const int ic_cast_white = 2130837653;
|
||||
|
||||
// aapt resource value: 0x7f020096
|
||||
public const int ic_cast_white = 2130837654;
|
||||
public const int ic_close_dark = 2130837654;
|
||||
|
||||
// aapt resource value: 0x7f020097
|
||||
public const int ic_close_dark = 2130837655;
|
||||
public const int ic_close_light = 2130837655;
|
||||
|
||||
// aapt resource value: 0x7f020098
|
||||
public const int ic_close_light = 2130837656;
|
||||
public const int ic_collapse = 2130837656;
|
||||
|
||||
// aapt resource value: 0x7f020099
|
||||
public const int ic_collapse = 2130837657;
|
||||
public const int ic_collapse_00000 = 2130837657;
|
||||
|
||||
// aapt resource value: 0x7f02009a
|
||||
public const int ic_collapse_00000 = 2130837658;
|
||||
public const int ic_collapse_00001 = 2130837658;
|
||||
|
||||
// aapt resource value: 0x7f02009b
|
||||
public const int ic_collapse_00001 = 2130837659;
|
||||
public const int ic_collapse_00002 = 2130837659;
|
||||
|
||||
// aapt resource value: 0x7f02009c
|
||||
public const int ic_collapse_00002 = 2130837660;
|
||||
public const int ic_collapse_00003 = 2130837660;
|
||||
|
||||
// aapt resource value: 0x7f02009d
|
||||
public const int ic_collapse_00003 = 2130837661;
|
||||
public const int ic_collapse_00004 = 2130837661;
|
||||
|
||||
// aapt resource value: 0x7f02009e
|
||||
public const int ic_collapse_00004 = 2130837662;
|
||||
public const int ic_collapse_00005 = 2130837662;
|
||||
|
||||
// aapt resource value: 0x7f02009f
|
||||
public const int ic_collapse_00005 = 2130837663;
|
||||
public const int ic_collapse_00006 = 2130837663;
|
||||
|
||||
// aapt resource value: 0x7f0200a0
|
||||
public const int ic_collapse_00006 = 2130837664;
|
||||
public const int ic_collapse_00007 = 2130837664;
|
||||
|
||||
// aapt resource value: 0x7f0200a1
|
||||
public const int ic_collapse_00007 = 2130837665;
|
||||
public const int ic_collapse_00008 = 2130837665;
|
||||
|
||||
// aapt resource value: 0x7f0200a2
|
||||
public const int ic_collapse_00008 = 2130837666;
|
||||
public const int ic_collapse_00009 = 2130837666;
|
||||
|
||||
// aapt resource value: 0x7f0200a3
|
||||
public const int ic_collapse_00009 = 2130837667;
|
||||
public const int ic_collapse_00010 = 2130837667;
|
||||
|
||||
// aapt resource value: 0x7f0200a4
|
||||
public const int ic_collapse_00010 = 2130837668;
|
||||
public const int ic_collapse_00011 = 2130837668;
|
||||
|
||||
// aapt resource value: 0x7f0200a5
|
||||
public const int ic_collapse_00011 = 2130837669;
|
||||
public const int ic_collapse_00012 = 2130837669;
|
||||
|
||||
// aapt resource value: 0x7f0200a6
|
||||
public const int ic_collapse_00012 = 2130837670;
|
||||
public const int ic_collapse_00013 = 2130837670;
|
||||
|
||||
// aapt resource value: 0x7f0200a7
|
||||
public const int ic_collapse_00013 = 2130837671;
|
||||
public const int ic_collapse_00014 = 2130837671;
|
||||
|
||||
// aapt resource value: 0x7f0200a8
|
||||
public const int ic_collapse_00014 = 2130837672;
|
||||
public const int ic_collapse_00015 = 2130837672;
|
||||
|
||||
// aapt resource value: 0x7f0200a9
|
||||
public const int ic_collapse_00015 = 2130837673;
|
||||
public const int ic_errorstatus = 2130837673;
|
||||
|
||||
// aapt resource value: 0x7f0200aa
|
||||
public const int ic_errorstatus = 2130837674;
|
||||
public const int ic_expand = 2130837674;
|
||||
|
||||
// aapt resource value: 0x7f0200ab
|
||||
public const int ic_expand = 2130837675;
|
||||
public const int ic_expand_00000 = 2130837675;
|
||||
|
||||
// aapt resource value: 0x7f0200ac
|
||||
public const int ic_expand_00000 = 2130837676;
|
||||
public const int ic_expand_00001 = 2130837676;
|
||||
|
||||
// aapt resource value: 0x7f0200ad
|
||||
public const int ic_expand_00001 = 2130837677;
|
||||
public const int ic_expand_00002 = 2130837677;
|
||||
|
||||
// aapt resource value: 0x7f0200ae
|
||||
public const int ic_expand_00002 = 2130837678;
|
||||
public const int ic_expand_00003 = 2130837678;
|
||||
|
||||
// aapt resource value: 0x7f0200af
|
||||
public const int ic_expand_00003 = 2130837679;
|
||||
public const int ic_expand_00004 = 2130837679;
|
||||
|
||||
// aapt resource value: 0x7f0200b0
|
||||
public const int ic_expand_00004 = 2130837680;
|
||||
public const int ic_expand_00005 = 2130837680;
|
||||
|
||||
// aapt resource value: 0x7f0200b1
|
||||
public const int ic_expand_00005 = 2130837681;
|
||||
public const int ic_expand_00006 = 2130837681;
|
||||
|
||||
// aapt resource value: 0x7f0200b2
|
||||
public const int ic_expand_00006 = 2130837682;
|
||||
public const int ic_expand_00007 = 2130837682;
|
||||
|
||||
// aapt resource value: 0x7f0200b3
|
||||
public const int ic_expand_00007 = 2130837683;
|
||||
public const int ic_expand_00008 = 2130837683;
|
||||
|
||||
// aapt resource value: 0x7f0200b4
|
||||
public const int ic_expand_00008 = 2130837684;
|
||||
public const int ic_expand_00009 = 2130837684;
|
||||
|
||||
// aapt resource value: 0x7f0200b5
|
||||
public const int ic_expand_00009 = 2130837685;
|
||||
public const int ic_expand_00010 = 2130837685;
|
||||
|
||||
// aapt resource value: 0x7f0200b6
|
||||
public const int ic_expand_00010 = 2130837686;
|
||||
public const int ic_expand_00011 = 2130837686;
|
||||
|
||||
// aapt resource value: 0x7f0200b7
|
||||
public const int ic_expand_00011 = 2130837687;
|
||||
public const int ic_expand_00012 = 2130837687;
|
||||
|
||||
// aapt resource value: 0x7f0200b8
|
||||
public const int ic_expand_00012 = 2130837688;
|
||||
public const int ic_expand_00013 = 2130837688;
|
||||
|
||||
// aapt resource value: 0x7f0200b9
|
||||
public const int ic_expand_00013 = 2130837689;
|
||||
public const int ic_expand_00014 = 2130837689;
|
||||
|
||||
// aapt resource value: 0x7f0200ba
|
||||
public const int ic_expand_00014 = 2130837690;
|
||||
public const int ic_expand_00015 = 2130837690;
|
||||
|
||||
// aapt resource value: 0x7f0200bb
|
||||
public const int ic_expand_00015 = 2130837691;
|
||||
public const int ic_media_pause = 2130837691;
|
||||
|
||||
// aapt resource value: 0x7f0200bc
|
||||
public const int ic_media_pause = 2130837692;
|
||||
public const int ic_media_play = 2130837692;
|
||||
|
||||
// aapt resource value: 0x7f0200bd
|
||||
public const int ic_media_play = 2130837693;
|
||||
public const int ic_media_route_disabled_mono_dark = 2130837693;
|
||||
|
||||
// aapt resource value: 0x7f0200be
|
||||
public const int ic_media_route_disabled_mono_dark = 2130837694;
|
||||
public const int ic_media_route_off_mono_dark = 2130837694;
|
||||
|
||||
// aapt resource value: 0x7f0200bf
|
||||
public const int ic_media_route_off_mono_dark = 2130837695;
|
||||
public const int ic_media_route_on_0_mono_dark = 2130837695;
|
||||
|
||||
// aapt resource value: 0x7f0200c0
|
||||
public const int ic_media_route_on_0_mono_dark = 2130837696;
|
||||
public const int ic_media_route_on_1_mono_dark = 2130837696;
|
||||
|
||||
// aapt resource value: 0x7f0200c1
|
||||
public const int ic_media_route_on_1_mono_dark = 2130837697;
|
||||
public const int ic_media_route_on_2_mono_dark = 2130837697;
|
||||
|
||||
// aapt resource value: 0x7f0200c2
|
||||
public const int ic_media_route_on_2_mono_dark = 2130837698;
|
||||
public const int ic_media_route_on_mono_dark = 2130837698;
|
||||
|
||||
// aapt resource value: 0x7f0200c3
|
||||
public const int ic_media_route_on_mono_dark = 2130837699;
|
||||
public const int ic_pause_dark = 2130837699;
|
||||
|
||||
// aapt resource value: 0x7f0200c4
|
||||
public const int ic_pause_dark = 2130837700;
|
||||
public const int ic_pause_light = 2130837700;
|
||||
|
||||
// aapt resource value: 0x7f0200c5
|
||||
public const int ic_pause_light = 2130837701;
|
||||
public const int ic_play_dark = 2130837701;
|
||||
|
||||
// aapt resource value: 0x7f0200c6
|
||||
public const int ic_play_dark = 2130837702;
|
||||
public const int ic_play_light = 2130837702;
|
||||
|
||||
// aapt resource value: 0x7f0200c7
|
||||
public const int ic_play_light = 2130837703;
|
||||
public const int ic_speaker_dark = 2130837703;
|
||||
|
||||
// aapt resource value: 0x7f0200c8
|
||||
public const int ic_speaker_dark = 2130837704;
|
||||
public const int ic_speaker_group_dark = 2130837704;
|
||||
|
||||
// aapt resource value: 0x7f0200c9
|
||||
public const int ic_speaker_group_dark = 2130837705;
|
||||
public const int ic_speaker_group_light = 2130837705;
|
||||
|
||||
// aapt resource value: 0x7f0200ca
|
||||
public const int ic_speaker_group_light = 2130837706;
|
||||
public const int ic_speaker_light = 2130837706;
|
||||
|
||||
// aapt resource value: 0x7f0200cb
|
||||
public const int ic_speaker_light = 2130837707;
|
||||
public const int ic_successstatus = 2130837707;
|
||||
|
||||
// aapt resource value: 0x7f0200cc
|
||||
public const int ic_successstatus = 2130837708;
|
||||
public const int ic_tv_dark = 2130837708;
|
||||
|
||||
// aapt resource value: 0x7f0200cd
|
||||
public const int ic_tv_dark = 2130837709;
|
||||
public const int ic_tv_light = 2130837709;
|
||||
|
||||
// aapt resource value: 0x7f0200ce
|
||||
public const int ic_tv_light = 2130837710;
|
||||
public const int icon = 2130837710;
|
||||
|
||||
// aapt resource value: 0x7f0200cf
|
||||
public const int icon = 2130837711;
|
||||
public const int ion_chevron_right = 2130837711;
|
||||
|
||||
// aapt resource value: 0x7f0200d0
|
||||
public const int ion_chevron_right = 2130837712;
|
||||
public const int lightbulb = 2130837712;
|
||||
|
||||
// aapt resource value: 0x7f0200d1
|
||||
public const int lightbulb = 2130837713;
|
||||
public const int list_selector = 2130837713;
|
||||
|
||||
// aapt resource value: 0x7f0200d2
|
||||
public const int list_selector = 2130837714;
|
||||
public const int @lock = 2130837714;
|
||||
|
||||
// aapt resource value: 0x7f0200d3
|
||||
public const int @lock = 2130837715;
|
||||
public const int logo = 2130837715;
|
||||
|
||||
// aapt resource value: 0x7f0200d4
|
||||
public const int logo = 2130837716;
|
||||
public const int more = 2130837716;
|
||||
|
||||
// aapt resource value: 0x7f0200d5
|
||||
public const int more = 2130837717;
|
||||
public const int mr_dialog_material_background_dark = 2130837717;
|
||||
|
||||
// aapt resource value: 0x7f0200d6
|
||||
public const int mr_dialog_material_background_dark = 2130837718;
|
||||
public const int mr_dialog_material_background_light = 2130837718;
|
||||
|
||||
// aapt resource value: 0x7f0200d7
|
||||
public const int mr_dialog_material_background_light = 2130837719;
|
||||
public const int mr_ic_audiotrack_light = 2130837719;
|
||||
|
||||
// aapt resource value: 0x7f0200d8
|
||||
public const int mr_ic_audiotrack_light = 2130837720;
|
||||
public const int mr_ic_cast_dark = 2130837720;
|
||||
|
||||
// aapt resource value: 0x7f0200d9
|
||||
public const int mr_ic_cast_dark = 2130837721;
|
||||
public const int mr_ic_cast_light = 2130837721;
|
||||
|
||||
// aapt resource value: 0x7f0200da
|
||||
public const int mr_ic_cast_light = 2130837722;
|
||||
public const int mr_ic_close_dark = 2130837722;
|
||||
|
||||
// aapt resource value: 0x7f0200db
|
||||
public const int mr_ic_close_dark = 2130837723;
|
||||
public const int mr_ic_close_light = 2130837723;
|
||||
|
||||
// aapt resource value: 0x7f0200dc
|
||||
public const int mr_ic_close_light = 2130837724;
|
||||
public const int mr_ic_media_route_connecting_mono_dark = 2130837724;
|
||||
|
||||
// aapt resource value: 0x7f0200dd
|
||||
public const int mr_ic_media_route_connecting_mono_dark = 2130837725;
|
||||
public const int mr_ic_media_route_connecting_mono_light = 2130837725;
|
||||
|
||||
// aapt resource value: 0x7f0200de
|
||||
public const int mr_ic_media_route_connecting_mono_light = 2130837726;
|
||||
public const int mr_ic_media_route_mono_dark = 2130837726;
|
||||
|
||||
// aapt resource value: 0x7f0200df
|
||||
public const int mr_ic_media_route_mono_dark = 2130837727;
|
||||
public const int mr_ic_media_route_mono_light = 2130837727;
|
||||
|
||||
// aapt resource value: 0x7f0200e0
|
||||
public const int mr_ic_media_route_mono_light = 2130837728;
|
||||
public const int mr_ic_pause_dark = 2130837728;
|
||||
|
||||
// aapt resource value: 0x7f0200e1
|
||||
public const int mr_ic_pause_dark = 2130837729;
|
||||
public const int mr_ic_pause_light = 2130837729;
|
||||
|
||||
// aapt resource value: 0x7f0200e2
|
||||
public const int mr_ic_pause_light = 2130837730;
|
||||
public const int mr_ic_play_dark = 2130837730;
|
||||
|
||||
// aapt resource value: 0x7f0200e3
|
||||
public const int mr_ic_play_dark = 2130837731;
|
||||
public const int mr_ic_play_light = 2130837731;
|
||||
|
||||
// aapt resource value: 0x7f0200e4
|
||||
public const int mr_ic_play_light = 2130837732;
|
||||
public const int notification_sm = 2130837732;
|
||||
|
||||
// aapt resource value: 0x7f0200f3
|
||||
public const int notification_template_icon_bg = 2130837747;
|
||||
|
||||
// aapt resource value: 0x7f0200e5
|
||||
public const int notification_sm = 2130837733;
|
||||
|
||||
// aapt resource value: 0x7f0200f1
|
||||
public const int notification_template_icon_bg = 2130837745;
|
||||
public const int plus = 2130837733;
|
||||
|
||||
// aapt resource value: 0x7f0200e6
|
||||
public const int plus = 2130837734;
|
||||
public const int refresh = 2130837734;
|
||||
|
||||
// aapt resource value: 0x7f0200e7
|
||||
public const int refresh = 2130837735;
|
||||
public const int roundedbg = 2130837735;
|
||||
|
||||
// aapt resource value: 0x7f0200e8
|
||||
public const int roundedbg = 2130837736;
|
||||
public const int roundedbgdark = 2130837736;
|
||||
|
||||
// aapt resource value: 0x7f0200e9
|
||||
public const int roundedbgdark = 2130837737;
|
||||
public const int search = 2130837737;
|
||||
|
||||
// aapt resource value: 0x7f0200ea
|
||||
public const int splash_screen = 2130837738;
|
||||
public const int share = 2130837738;
|
||||
|
||||
// aapt resource value: 0x7f0200eb
|
||||
public const int star = 2130837739;
|
||||
public const int share_tools = 2130837739;
|
||||
|
||||
// aapt resource value: 0x7f0200ec
|
||||
public const int star_selected = 2130837740;
|
||||
public const int splash_screen = 2130837740;
|
||||
|
||||
// aapt resource value: 0x7f0200ed
|
||||
public const int tools = 2130837741;
|
||||
public const int star = 2130837741;
|
||||
|
||||
// aapt resource value: 0x7f0200ee
|
||||
public const int tools_selected = 2130837742;
|
||||
public const int star_selected = 2130837742;
|
||||
|
||||
// aapt resource value: 0x7f0200ef
|
||||
public const int upload = 2130837743;
|
||||
public const int tools = 2130837743;
|
||||
|
||||
// aapt resource value: 0x7f0200f0
|
||||
public const int user = 2130837744;
|
||||
public const int tools_selected = 2130837744;
|
||||
|
||||
// aapt resource value: 0x7f0200f1
|
||||
public const int upload = 2130837745;
|
||||
|
||||
// aapt resource value: 0x7f0200f2
|
||||
public const int user = 2130837746;
|
||||
|
||||
static Drawable()
|
||||
{
|
||||
|
||||
|
Before Width: | Height: | Size: 347 B |
BIN
src/Android/Resources/drawable-hdpi/search.png
Normal file
|
After Width: | Height: | Size: 519 B |
BIN
src/Android/Resources/drawable-hdpi/share.png
Normal file
|
After Width: | Height: | Size: 403 B |
BIN
src/Android/Resources/drawable-hdpi/share_tools.png
Normal file
|
After Width: | Height: | Size: 1009 B |
|
Before Width: | Height: | Size: 404 B |
BIN
src/Android/Resources/drawable-xhdpi/search.png
Normal file
|
After Width: | Height: | Size: 693 B |
BIN
src/Android/Resources/drawable-xhdpi/share.png
Normal file
|
After Width: | Height: | Size: 503 B |
BIN
src/Android/Resources/drawable-xhdpi/share_tools.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 374 B |
BIN
src/Android/Resources/drawable-xxhdpi/search.png
Normal file
|
After Width: | Height: | Size: 957 B |
BIN
src/Android/Resources/drawable-xxhdpi/share.png
Normal file
|
After Width: | Height: | Size: 649 B |
BIN
src/Android/Resources/drawable-xxhdpi/share_tools.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 564 B |
BIN
src/Android/Resources/drawable-xxxhdpi/search.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/Android/Resources/drawable-xxxhdpi/share.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/Android/Resources/drawable-xxxhdpi/share_tools.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 312 B |
BIN
src/Android/Resources/drawable/search.png
Normal file
|
After Width: | Height: | Size: 449 B |
BIN
src/Android/Resources/drawable/share.png
Normal file
|
After Width: | Height: | Size: 298 B |
BIN
src/Android/Resources/drawable/share_tools.png
Normal file
|
After Width: | Height: | Size: 817 B |
@@ -3,6 +3,6 @@
|
||||
android:description="@string/AutoFillServiceDescription"
|
||||
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:accessibilityFlags="flagDefault"
|
||||
android:accessibilityFlags="flagReportViewIds"
|
||||
android:notificationTimeout="100"
|
||||
android:canRetrieveWindowContent="true"/>
|
||||
@@ -1,4 +1,6 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Android.App;
|
||||
using Bit.App.Abstractions;
|
||||
using System.Linq;
|
||||
using AndroidApp = Android.App.Application;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
@@ -11,6 +13,14 @@ namespace Bit.Android.Services
|
||||
public string Build => AndroidApp.Context.ApplicationContext.PackageManager
|
||||
.GetPackageInfo(AndroidApp.Context.PackageName, 0).VersionCode.ToString();
|
||||
|
||||
public bool AutofillServiceEnabled => AutofillService.Enabled;
|
||||
public bool AutofillServiceEnabled => AutofillRunning();
|
||||
|
||||
private bool AutofillRunning()
|
||||
{
|
||||
var manager = ((ActivityManager)Xamarin.Forms.Forms.Context.GetSystemService("activity"));
|
||||
var services = manager.GetRunningServices(int.MaxValue);
|
||||
return services.Any(s => s.Process.ToLowerInvariant().Contains("bitwarden") &&
|
||||
s.Service.ClassName.ToLowerInvariant().Contains("autofill"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using Bit.App;
|
||||
using Bit.App.Abstractions;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Android.Gms.Analytics;
|
||||
using Android.Content;
|
||||
|
||||
@@ -7,8 +9,6 @@ namespace Bit.Android.Services
|
||||
{
|
||||
public class GoogleAnalyticsService : IGoogleAnalyticsService
|
||||
{
|
||||
private const string UserId = "&uid";
|
||||
|
||||
private readonly GoogleAnalytics _instance;
|
||||
private readonly IAuthService _authService;
|
||||
private readonly Tracker _tracker;
|
||||
@@ -17,7 +17,8 @@ namespace Bit.Android.Services
|
||||
public GoogleAnalyticsService(
|
||||
Context appContext,
|
||||
IAppIdService appIdService,
|
||||
IAuthService authService)
|
||||
IAuthService authService,
|
||||
ISettings settings)
|
||||
{
|
||||
_authService = authService;
|
||||
|
||||
@@ -25,16 +26,13 @@ namespace Bit.Android.Services
|
||||
_instance.SetLocalDispatchPeriod(10);
|
||||
|
||||
_tracker = _instance.NewTracker("UA-81915606-2");
|
||||
_tracker.EnableExceptionReporting(true);
|
||||
_tracker.EnableExceptionReporting(false);
|
||||
_tracker.EnableAdvertisingIdCollection(true);
|
||||
_tracker.EnableAutoActivityTracking(true);
|
||||
_tracker.SetClientId(appIdService.AnonymousAppId);
|
||||
}
|
||||
|
||||
public void RefreshUserId()
|
||||
{
|
||||
_tracker.Set(UserId, null);
|
||||
_setUserId = true;
|
||||
var gaOptOut = settings.GetValueOrDefault(Constants.SettingGaOptOut, false);
|
||||
SetAppOptOut(gaOptOut);
|
||||
}
|
||||
|
||||
public void TrackAppEvent(string eventName, string label = null)
|
||||
@@ -57,7 +55,6 @@ namespace Bit.Android.Services
|
||||
builder.SetLabel(label);
|
||||
}
|
||||
|
||||
SetUserId();
|
||||
_tracker.Send(builder.Build());
|
||||
}
|
||||
|
||||
@@ -67,30 +64,24 @@ namespace Bit.Android.Services
|
||||
builder.SetDescription(message);
|
||||
builder.SetFatal(fatal);
|
||||
|
||||
SetUserId();
|
||||
_tracker.Send(builder.Build());
|
||||
}
|
||||
|
||||
public void TrackPage(string pageName)
|
||||
{
|
||||
SetUserId();
|
||||
_tracker.SetScreenName(pageName);
|
||||
_tracker.Send(new HitBuilders.ScreenViewBuilder().Build());
|
||||
}
|
||||
|
||||
private void SetUserId()
|
||||
{
|
||||
if(_setUserId && _authService.IsAuthenticated)
|
||||
{
|
||||
_tracker.Set(UserId, _authService.UserId);
|
||||
_setUserId = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispatch(Action completionHandler = null)
|
||||
{
|
||||
_instance.DispatchLocalHits();
|
||||
completionHandler?.Invoke();
|
||||
}
|
||||
|
||||
public void SetAppOptOut(bool optOut)
|
||||
{
|
||||
_instance.AppOptOut = optOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Bit.Android.Services
|
||||
{
|
||||
public class HttpService : IHttpService
|
||||
{
|
||||
public ApiHttpClient Client => new ApiHttpClient(new CustomAndroidClientHandler());
|
||||
public ApiHttpClient ApiClient => new ApiHttpClient(new AndroidClientHandler());
|
||||
public IdentityHttpClient IdentityClient => new IdentityHttpClient(new AndroidClientHandler());
|
||||
}
|
||||
}
|
||||
|
||||
44
src/Android/Services/MemoryService.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
public class MemoryService : IMemoryService
|
||||
{
|
||||
public MemoryInfo GetInfo()
|
||||
{
|
||||
return MemoryHelper.GetMemoryInfo(Forms.Context);
|
||||
}
|
||||
|
||||
public void Check()
|
||||
{
|
||||
MemoryHelper.MemoryCheck(Forms.Context);
|
||||
}
|
||||
|
||||
public static class MemoryHelper
|
||||
{
|
||||
public static void MemoryCheck(Context context)
|
||||
{
|
||||
Console.WriteLine("MemoryHelper.MemoryCheck.{0} - {1}", "Start", context.ToString());
|
||||
var maxMemory = Java.Lang.Runtime.GetRuntime().MaxMemory();
|
||||
var freeMemory = Java.Lang.Runtime.GetRuntime().FreeMemory();
|
||||
var percentUsed = (maxMemory - freeMemory) / (double)maxMemory;
|
||||
Console.WriteLine("Free memory: {0:N}", freeMemory);
|
||||
Console.WriteLine("Max memory: {0:N}", maxMemory);
|
||||
Console.WriteLine("% used: {0:P}", percentUsed);
|
||||
Console.WriteLine("MemoryHelper.MemoryCheck.{0} {3:P} {1} out of {2}", "End", freeMemory, maxMemory, percentUsed);
|
||||
}
|
||||
|
||||
public static MemoryInfo GetMemoryInfo(Context context)
|
||||
{
|
||||
var retVal = new MemoryInfo();
|
||||
retVal.MaxMemory = Java.Lang.Runtime.GetRuntime().MaxMemory();
|
||||
retVal.FreeMemory = Java.Lang.Runtime.GetRuntime().FreeMemory();
|
||||
retVal.TotalMemory = Java.Lang.Runtime.GetRuntime().TotalMemory();
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Acr.Support" version="2.1.0" targetFramework="monoandroid60" />
|
||||
<package id="Acr.UserDialogs" version="6.3.3" targetFramework="monoandroid60" />
|
||||
<package id="Acr.UserDialogs" version="6.3.10" targetFramework="monoandroid71" />
|
||||
<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.0" targetFramework="monoandroid60" />
|
||||
<package id="HockeySDK.Xamarin" version="4.1.2" 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" />
|
||||
@@ -25,9 +25,9 @@
|
||||
<package id="SQLitePCLRaw.provider.e_sqlite3.android" version="1.1.2" targetFramework="monoandroid60" />
|
||||
<package id="Unity" version="3.5.1405-prerelease" targetFramework="monoandroid60" />
|
||||
<package id="Validation" version="2.3.7" targetFramework="monoandroid60" />
|
||||
<package id="Xam.Plugin.Connectivity" version="2.2.12" targetFramework="monoandroid60" />
|
||||
<package id="Xam.Plugin.Connectivity" version="2.3.0" targetFramework="monoandroid71" />
|
||||
<package id="Xam.Plugin.PushNotification" version="1.2.4" targetFramework="monoandroid60" developmentDependency="true" />
|
||||
<package id="Xam.Plugins.Settings" version="2.5.1.0" targetFramework="monoandroid60" />
|
||||
<package id="Xam.Plugins.Settings" version="2.5.4" 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" />
|
||||
@@ -36,7 +36,9 @@
|
||||
<package id="Xamarin.Android.Support.v7.MediaRouter" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.Android.Support.v7.RecyclerView" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.Android.Support.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.Forms" version="2.3.3.180" 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.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" />
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Bit.App.Abstractions
|
||||
{
|
||||
Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj);
|
||||
Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj);
|
||||
Task<ApiResult<DateTime?>> GetAccountRevisionDate();
|
||||
Task<ApiResult<DateTime?>> GetAccountRevisionDateAsync();
|
||||
Task<ApiResult<ProfileResponse>> GetProfileAsync();
|
||||
Task<ApiResult<KeysResponse>> GetKeys();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,5 @@ namespace Bit.App.Abstractions
|
||||
{
|
||||
Task<ApiResult<CipherResponse>> GetByIdAsync(string id);
|
||||
Task<ApiResult<ListResponse<CipherResponse>>> GetAsync();
|
||||
Task<ApiResult<CipherHistoryResponse>> GetByRevisionDateWithHistoryAsync(DateTime since);
|
||||
}
|
||||
}
|
||||
10
src/App/Abstractions/Services/IAppSettingsService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IAppSettingsService
|
||||
{
|
||||
bool Locked { get; set; }
|
||||
DateTime LastActivity { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models.Api;
|
||||
using System;
|
||||
using Bit.App.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
@@ -13,7 +12,9 @@ namespace Bit.App.Abstractions
|
||||
string Email { get; set; }
|
||||
string PIN { get; set; }
|
||||
|
||||
bool BelongsToOrganization(string orgId);
|
||||
void LogOut();
|
||||
Task<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request);
|
||||
Task<FullLoginResult> TokenPostAsync(string email, string masterPassword);
|
||||
Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, SymmetricCryptoKey key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Models.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface ICryptoService
|
||||
{
|
||||
string Base64Key { get; }
|
||||
byte[] Key { get; set; }
|
||||
byte[] PreviousKey { get; }
|
||||
SymmetricCryptoKey Key { get; set; }
|
||||
SymmetricCryptoKey PreviousKey { get; }
|
||||
bool KeyChanged { get; }
|
||||
byte[] PrivateKey { get; }
|
||||
IDictionary<string, SymmetricCryptoKey> OrgKeys { get; }
|
||||
|
||||
string Decrypt(CipherString encyptedValue);
|
||||
CipherString Encrypt(string plaintextValue);
|
||||
byte[] MakeKeyFromPassword(string password, string salt);
|
||||
void SetPrivateKey(CipherString privateKeyEnc);
|
||||
void SetOrgKeys(ProfileResponse profile);
|
||||
void SetOrgKeys(Dictionary<string, string> orgKeysEncDict);
|
||||
SymmetricCryptoKey GetOrgKey(string orgId);
|
||||
void ClearKeys();
|
||||
string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null);
|
||||
byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null);
|
||||
byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey);
|
||||
CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null);
|
||||
SymmetricCryptoKey MakeKeyFromPassword(string password, string salt);
|
||||
string MakeKeyFromPasswordBase64(string password, string salt);
|
||||
byte[] HashPassword(byte[] key, string password);
|
||||
string HashPasswordBase64(byte[] key, string password);
|
||||
byte[] HashPassword(SymmetricCryptoKey key, string password);
|
||||
string HashPasswordBase64(SymmetricCryptoKey key, string password);
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IGoogleAnalyticsService
|
||||
{
|
||||
void RefreshUserId();
|
||||
void TrackPage(string pageName);
|
||||
void TrackAppEvent(string eventName, string label = null);
|
||||
void TrackExtensionEvent(string eventName, string label = null);
|
||||
void TrackEvent(string category, string eventName, string label = null);
|
||||
void TrackException(string message, bool fatal);
|
||||
void Dispatch(Action completionHandler = null);
|
||||
void SetAppOptOut(bool optOut);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
public interface IHttpService
|
||||
{
|
||||
ApiHttpClient Client { get; }
|
||||
ApiHttpClient ApiClient { get; }
|
||||
IdentityHttpClient IdentityClient { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Bit.App.Enums;
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface ILockService
|
||||
{
|
||||
void UpdateLastActivity(DateTime? activityDate = null);
|
||||
LockType GetLockType(bool forceLock);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Models.Api;
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
@@ -10,7 +11,7 @@ namespace Bit.App.Abstractions
|
||||
Task<Login> GetByIdAsync(string id);
|
||||
Task<IEnumerable<Login>> GetAllAsync();
|
||||
Task<IEnumerable<Login>> GetAllAsync(bool favorites);
|
||||
Task<IEnumerable<Login>> GetAllAsync(string uriString);
|
||||
Task<Tuple<IEnumerable<Login>, IEnumerable<Login>>> GetAllAsync(string uriString);
|
||||
Task<ApiResult<LoginResponse>> SaveAsync(Login login);
|
||||
Task<ApiResult> DeleteAsync(string id);
|
||||
}
|
||||
|
||||
26
src/App/Abstractions/Services/IMemoryService.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IMemoryService
|
||||
{
|
||||
MemoryInfo GetInfo();
|
||||
void Check();
|
||||
}
|
||||
|
||||
public class MemoryInfo
|
||||
{
|
||||
public long FreeMemory { get; set; }
|
||||
public long MaxMemory { get; set; }
|
||||
public long TotalMemory { get; set; }
|
||||
public long UsedMemory => TotalMemory - FreeMemory;
|
||||
|
||||
public double HeapUsage()
|
||||
{
|
||||
return UsedMemory / (double)TotalMemory;
|
||||
}
|
||||
|
||||
public double Usage()
|
||||
{
|
||||
return UsedMemory / (double)MaxMemory;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,12 @@ namespace Bit.App.Abstractions
|
||||
public interface ISyncService
|
||||
{
|
||||
bool SyncInProgress { get; }
|
||||
Task<bool> SyncAsync(string id);
|
||||
Task<bool> SyncCipherAsync(string id);
|
||||
Task<bool> SyncFolderAsync(string id);
|
||||
Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate);
|
||||
Task<bool> SyncDeleteLoginAsync(string id);
|
||||
Task<bool> SyncSettingsAsync();
|
||||
Task<bool> SyncProfileAsync();
|
||||
Task<bool> FullSyncAsync(bool forceSync = false);
|
||||
Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Bit.App.Abstractions
|
||||
[Obsolete("Old auth scheme")]
|
||||
string AuthBearer { get; set; }
|
||||
DateTime TokenExpiration { get; }
|
||||
string TokenIssuer { get; }
|
||||
bool TokenExpired { get; }
|
||||
TimeSpan TokenTimeRemaining { get; }
|
||||
bool TokenNeedsRefresh { get; }
|
||||
|
||||
@@ -14,7 +14,6 @@ using Acr.UserDialogs;
|
||||
using XLabs.Ioc;
|
||||
using System.Reflection;
|
||||
using Bit.App.Resources;
|
||||
using System.Threading;
|
||||
|
||||
namespace Bit.App
|
||||
{
|
||||
@@ -34,6 +33,7 @@ namespace Bit.App
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly ILocalizeService _localizeService;
|
||||
private readonly IAppInfoService _appInfoService;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
|
||||
public App(
|
||||
string uri,
|
||||
@@ -47,7 +47,8 @@ namespace Bit.App
|
||||
ILockService lockService,
|
||||
IGoogleAnalyticsService googleAnalyticsService,
|
||||
ILocalizeService localizeService,
|
||||
IAppInfoService appInfoService)
|
||||
IAppInfoService appInfoService,
|
||||
IAppSettingsService appSettingsService)
|
||||
{
|
||||
_uri = uri;
|
||||
_databaseService = databaseService;
|
||||
@@ -61,6 +62,7 @@ namespace Bit.App
|
||||
_googleAnalyticsService = googleAnalyticsService;
|
||||
_localizeService = localizeService;
|
||||
_appInfoService = appInfoService;
|
||||
_appSettingsService = appSettingsService;
|
||||
|
||||
SetCulture();
|
||||
SetStyles();
|
||||
@@ -80,7 +82,7 @@ namespace Bit.App
|
||||
|
||||
MessagingCenter.Subscribe<Application, bool>(Current, "Resumed", async (sender, args) =>
|
||||
{
|
||||
await CheckLockAsync(args);
|
||||
Device.BeginInvokeOnMainThread(async () => await CheckLockAsync(args));
|
||||
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
@@ -91,12 +93,7 @@ namespace Bit.App
|
||||
|
||||
MessagingCenter.Subscribe<Application, string>(Current, "Logout", (sender, args) =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() => Logout(args));
|
||||
});
|
||||
|
||||
MessagingCenter.Subscribe<Application>(Current, "SetMainPage", (sender) =>
|
||||
{
|
||||
SetMainPageFromAutofill();
|
||||
Logout(args);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,9 +123,10 @@ namespace Bit.App
|
||||
Debug.WriteLine("OnSleep");
|
||||
|
||||
SetMainPageFromAutofill();
|
||||
|
||||
if(Device.OS == TargetPlatform.Android && !TopPageIsLock())
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
|
||||
_lockService.UpdateLastActivity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +158,21 @@ namespace Bit.App
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMainPageFromAutofill()
|
||||
{
|
||||
if(Device.OS == TargetPlatform.Android && !string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
Current.MainPage = new MainPage();
|
||||
_uri = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool InDebugMode()
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -169,17 +182,6 @@ namespace Bit.App
|
||||
#endif
|
||||
}
|
||||
|
||||
private void SetMainPageFromAutofill()
|
||||
{
|
||||
if(Device.OS != TargetPlatform.Android || string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MainPage = new MainPage();
|
||||
_uri = null;
|
||||
}
|
||||
|
||||
private async Task FullSyncAsync()
|
||||
{
|
||||
if(_connectivity.IsConnected)
|
||||
@@ -222,19 +224,17 @@ namespace Bit.App
|
||||
{
|
||||
_authService.LogOut();
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedOut");
|
||||
_googleAnalyticsService.RefreshUserId();
|
||||
var deviceApiRepository = Resolver.Resolve<IDeviceApiRepository>();
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
await Task.Run(() => deviceApiRepository.PutClearTokenAsync(appIdService.AppId)).ConfigureAwait(false);
|
||||
|
||||
Current.MainPage = new ExtendedNavigationPage(new HomePage());
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedOut");
|
||||
|
||||
Device.BeginInvokeOnMainThread(() => Current.MainPage = new ExtendedNavigationPage(new HomePage()));
|
||||
if(!string.IsNullOrWhiteSpace(logoutMessage))
|
||||
{
|
||||
_userDialogs.Toast(logoutMessage);
|
||||
}
|
||||
|
||||
var deviceApiRepository = Resolver.Resolve<IDeviceApiRepository>();
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
_settings.Remove(Constants.PushLastRegistrationDate);
|
||||
await Task.Run(() => deviceApiRepository.PutClearTokenAsync(appIdService.AppId)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task CheckLockAsync(bool forceLock)
|
||||
@@ -246,7 +246,12 @@ namespace Bit.App
|
||||
}
|
||||
|
||||
var lockType = _lockService.GetLockType(forceLock);
|
||||
var currentPage = Current.MainPage.Navigation.ModalStack.LastOrDefault() as ExtendedNavigationPage;
|
||||
if(lockType == Enums.LockType.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appSettingsService.Locked = true;
|
||||
switch(lockType)
|
||||
{
|
||||
case Enums.LockType.Fingerprint:
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
<Compile Include="Abstractions\Repositories\IAccountsApiRepository.cs" />
|
||||
<Compile Include="Abstractions\Repositories\IDeviceApiRepository.cs" />
|
||||
<Compile Include="Abstractions\Repositories\ISettingsRepository.cs" />
|
||||
<Compile Include="Abstractions\Services\IAppSettingsService.cs" />
|
||||
<Compile Include="Abstractions\Services\IMemoryService.cs" />
|
||||
<Compile Include="Abstractions\Services\ISettingsService.cs" />
|
||||
<Compile Include="Abstractions\Services\ITokenService.cs" />
|
||||
<Compile Include="Abstractions\Services\IHttpService.cs" />
|
||||
@@ -57,11 +59,13 @@
|
||||
<Compile Include="Abstractions\Services\ISecureStorageService.cs" />
|
||||
<Compile Include="Abstractions\Services\ISqlService.cs" />
|
||||
<Compile Include="Constants.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\MemoryContentView.cs" />
|
||||
<Compile Include="Controls\StepperCell.cs" />
|
||||
<Compile Include="Controls\ExtendedTableView.cs" />
|
||||
<Compile Include="Controls\ExtendedPicker.cs" />
|
||||
@@ -77,8 +81,11 @@
|
||||
<Compile Include="Controls\FormEntryCell.cs" />
|
||||
<Compile Include="Controls\PinControl.cs" />
|
||||
<Compile Include="Controls\VaultListViewCell.cs" />
|
||||
<Compile Include="Enums\EncryptionType.cs" />
|
||||
<Compile Include="Enums\OrganizationUserType.cs" />
|
||||
<Compile Include="Enums\LockType.cs" />
|
||||
<Compile Include="Enums\CipherType.cs" />
|
||||
<Compile Include="Enums\OrganizationUserStatusType.cs" />
|
||||
<Compile Include="Enums\PushType.cs" />
|
||||
<Compile Include="Enums\ReturnType.cs" />
|
||||
<Compile Include="Abstractions\Services\ILocalizeService.cs" />
|
||||
@@ -100,17 +107,21 @@
|
||||
<Compile Include="Models\Api\Response\ListResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\DeviceResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\LoginResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\ProfileOrganizationResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\KeysResponse.cs" />
|
||||
<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\SymmetricCryptoKey.cs" />
|
||||
<Compile Include="Models\Data\SettingsData.cs" />
|
||||
<Compile Include="Models\Data\FolderData.cs" />
|
||||
<Compile Include="Abstractions\IDataObject.cs" />
|
||||
<Compile Include="Models\Data\LoginData.cs" />
|
||||
<Compile Include="Models\DomainName.cs" />
|
||||
<Compile Include="Models\Folder.cs" />
|
||||
<Compile Include="Models\LoginResult.cs" />
|
||||
<Compile Include="Models\Page\AppExtensionPageModel.cs" />
|
||||
<Compile Include="Models\Page\SettingsFolderPageModel.cs" />
|
||||
<Compile Include="Models\Page\PinPageModel.cs" />
|
||||
@@ -193,6 +204,7 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AppResources.zh-Hans.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Services\AppSettingsService.cs" />
|
||||
<Compile Include="Services\SettingsService.cs" />
|
||||
<Compile Include="Services\TokenService.cs" />
|
||||
<Compile Include="Services\AppIdService.cs" />
|
||||
@@ -217,6 +229,7 @@
|
||||
<Compile Include="Pages\Vault\VaultEditLoginPage.cs" />
|
||||
<Compile Include="Pages\Vault\VaultListLoginsPage.cs" />
|
||||
<Compile Include="Services\PasswordGenerationService.cs" />
|
||||
<Compile Include="Utilities\IdentityHttpClient.cs" />
|
||||
<Compile Include="Utilities\Extentions.cs" />
|
||||
<Compile Include="Utilities\ExtendedObservableCollection.cs" />
|
||||
<Compile Include="Utilities\ApiHttpClient.cs" />
|
||||
@@ -251,16 +264,22 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Acr.UserDialogs, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.10\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Acr.UserDialogs.Interface, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.Interface.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.10\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.Interface.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="HockeySDK, Version=1.0.6103.22141, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.0\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\HockeySDK.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="FFImageLoading, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.9\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Forms, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.9\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Forms.dll</HintPath>
|
||||
</Reference>
|
||||
<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>
|
||||
<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>
|
||||
@@ -297,13 +316,11 @@
|
||||
<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>
|
||||
<Reference Include="Plugin.Connectivity, Version=2.2.12.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\portable-net45+wp80+wp81+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+Xamarin.Mac20+UAP10\Plugin.Connectivity.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="Plugin.Connectivity.Abstractions, Version=2.2.12.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugin.Connectivity.2.2.12\lib\portable-net45+wp80+wp81+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+Xamarin.Mac20+UAP10\Plugin.Connectivity.Abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="Plugin.Fingerprint, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.2.0\lib\portable-net45+win8+wpa81+wp8\Plugin.Fingerprint.dll</HintPath>
|
||||
@@ -313,13 +330,11 @@
|
||||
<HintPath>..\..\packages\Plugin.Fingerprint.1.2.0\lib\portable-net45+win8+wpa81+wp8\Plugin.Fingerprint.Abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Plugin.Settings, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="Plugin.Settings.Abstractions, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xam.Plugins.Settings.2.5.1.0\lib\portable-net45+wp80+win8+wpa81\Plugin.Settings.Abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<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>
|
||||
@@ -362,16 +377,13 @@
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\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.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\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.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.4.231\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>
|
||||
@@ -388,12 +400,12 @@
|
||||
<Compile Include="Services\PushNotificationListener.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
<Import Project="..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.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')" />
|
||||
<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.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.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'))" />
|
||||
</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.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
public const string SettingFingerprintUnlockOn = "setting:fingerprintUnlockOn";
|
||||
public const string SettingPinUnlockOn = "setting:pinUnlockOn";
|
||||
public const string SettingLockSeconds = "setting:lockSeconds";
|
||||
public const string SettingGaOptOut = "setting:googleAnalyticsOptOut";
|
||||
|
||||
public const string PasswordGeneratorLength = "pwGenerator:length";
|
||||
public const string PasswordGeneratorUppercase = "pwGenerator:uppercase";
|
||||
|
||||
@@ -4,23 +4,23 @@ using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class DismissModalToolBarItem : ToolbarItem
|
||||
public class DismissModalToolBarItem : ExtendedToolbarItem, IDisposable
|
||||
{
|
||||
private readonly ContentPage _page;
|
||||
private readonly Action _cancelClickedAction;
|
||||
|
||||
public DismissModalToolBarItem(ContentPage page, string text = null, Action cancelClickedAction = null)
|
||||
: base(cancelClickedAction)
|
||||
{
|
||||
_cancelClickedAction = cancelClickedAction;
|
||||
_page = page;
|
||||
// TODO: init and dispose events from pages
|
||||
InitEvents();
|
||||
Text = text ?? AppResources.Close;
|
||||
Clicked += ClickedItem;
|
||||
Priority = -1;
|
||||
}
|
||||
|
||||
private async void ClickedItem(object sender, EventArgs e)
|
||||
protected async override void ClickedItem(object sender, EventArgs e)
|
||||
{
|
||||
_cancelClickedAction?.Invoke();
|
||||
base.ClickedItem(sender, e);
|
||||
await _page.Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Bit.App.Controls
|
||||
{
|
||||
private ISyncService _syncService;
|
||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private ISettings _settings;
|
||||
private ILockService _lockService;
|
||||
private bool _syncIndicator;
|
||||
private bool _updateActivity;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Bit.App.Controls
|
||||
_updateActivity = updateActivity;
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_lockService = Resolver.Resolve<ILockService>();
|
||||
|
||||
BackgroundColor = Color.FromHex("efeff4");
|
||||
|
||||
@@ -45,11 +45,6 @@ namespace Bit.App.Controls
|
||||
IsBusy = _syncService.SyncInProgress;
|
||||
}
|
||||
|
||||
if(_updateActivity)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackPage(GetType().Name);
|
||||
base.OnAppearing();
|
||||
}
|
||||
@@ -61,6 +56,11 @@ namespace Bit.App.Controls
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
if(_updateActivity)
|
||||
{
|
||||
_lockService.UpdateLastActivity();
|
||||
}
|
||||
|
||||
base.OnDisappearing();
|
||||
}
|
||||
}
|
||||
|
||||
30
src/App/Controls/ExtendedToolbarItem.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedToolbarItem : ToolbarItem, IDisposable
|
||||
{
|
||||
public ExtendedToolbarItem(Action clickAction = null)
|
||||
{
|
||||
ClickAction = clickAction;
|
||||
}
|
||||
|
||||
public Action ClickAction { get; set; }
|
||||
|
||||
protected virtual void ClickedItem(object sender, EventArgs e)
|
||||
{
|
||||
ClickAction?.Invoke();
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
Clicked += ClickedItem;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clicked -= ClickedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class FormEditorCell : ExtendedViewCell
|
||||
public class FormEditorCell : ExtendedViewCell, IDisposable
|
||||
{
|
||||
public FormEditorCell(Keyboard entryKeyboard = null, double? height = null)
|
||||
{
|
||||
@@ -26,7 +26,6 @@ namespace Bit.App.Controls
|
||||
|
||||
stackLayout.Children.Add(Editor);
|
||||
|
||||
Tapped += FormEditorCell_Tapped;
|
||||
Editor.AdjustMarginsForDevice();
|
||||
stackLayout.AdjustPaddingForDevice();
|
||||
|
||||
@@ -39,5 +38,15 @@ namespace Bit.App.Controls
|
||||
{
|
||||
Editor.Focus();
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
Tapped += FormEditorCell_Tapped;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tapped -= FormEditorCell_Tapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using Bit.App.Abstractions;
|
||||
using FFImageLoading.Forms;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class FormEntryCell : ExtendedViewCell
|
||||
public class FormEntryCell : ExtendedViewCell, IDisposable
|
||||
{
|
||||
private VisualElement _nextElement;
|
||||
private TapGestureRecognizer _tgr;
|
||||
|
||||
public FormEntryCell(
|
||||
string labelText,
|
||||
Keyboard entryKeyboard = null,
|
||||
@@ -17,6 +21,8 @@ namespace Bit.App.Controls
|
||||
Thickness? containerPadding = null,
|
||||
bool useButton = false)
|
||||
{
|
||||
_nextElement = nextElement;
|
||||
|
||||
if(!useLabelAsPlaceholder)
|
||||
{
|
||||
Label = new Label
|
||||
@@ -47,7 +53,6 @@ namespace Bit.App.Controls
|
||||
if(nextElement != null)
|
||||
{
|
||||
Entry.ReturnType = Enums.ReturnType.Next;
|
||||
Entry.Completed += (object sender, EventArgs e) => { nextElement.Focus(); };
|
||||
}
|
||||
|
||||
var imageStackLayout = new StackLayout
|
||||
@@ -61,16 +66,17 @@ namespace Bit.App.Controls
|
||||
|
||||
if(imageSource != null)
|
||||
{
|
||||
var tgr = new TapGestureRecognizer();
|
||||
tgr.Tapped += Tgr_Tapped;
|
||||
_tgr = new TapGestureRecognizer();
|
||||
|
||||
var theImage = new Image
|
||||
var theImage = new CachedImage
|
||||
{
|
||||
Source = imageSource,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 18,
|
||||
HeightRequest = 18
|
||||
};
|
||||
theImage.GestureRecognizers.Add(tgr);
|
||||
theImage.GestureRecognizers.Add(_tgr);
|
||||
|
||||
imageStackLayout.Children.Add(theImage);
|
||||
}
|
||||
@@ -126,8 +132,6 @@ namespace Bit.App.Controls
|
||||
}
|
||||
}
|
||||
|
||||
Tapped += FormEntryCell_Tapped;
|
||||
|
||||
View = imageStackLayout;
|
||||
}
|
||||
|
||||
@@ -135,6 +139,21 @@ namespace Bit.App.Controls
|
||||
public ExtendedEntry Entry { get; private set; }
|
||||
public ExtendedButton Button { get; private set; }
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
if(_nextElement != null)
|
||||
{
|
||||
Entry.Completed += Entry_Completed;
|
||||
}
|
||||
|
||||
if(_tgr != null)
|
||||
{
|
||||
_tgr.Tapped += Tgr_Tapped;
|
||||
}
|
||||
|
||||
Tapped += FormEntryCell_Tapped;
|
||||
}
|
||||
|
||||
private void Tgr_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Entry.Focus();
|
||||
@@ -144,5 +163,21 @@ namespace Bit.App.Controls
|
||||
{
|
||||
Entry.Focus();
|
||||
}
|
||||
|
||||
private void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
_nextElement?.Focus();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(_tgr != null)
|
||||
{
|
||||
_tgr.Tapped -= Tgr_Tapped;
|
||||
}
|
||||
|
||||
Tapped -= FormEntryCell_Tapped;
|
||||
Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
@@ -42,8 +41,6 @@ namespace Bit.App.Controls
|
||||
Picker.AdjustMarginsForDevice();
|
||||
stackLayout.AdjustPaddingForDevice();
|
||||
|
||||
Tapped += FormPickerCell_Tapped;
|
||||
|
||||
View = stackLayout;
|
||||
}
|
||||
|
||||
@@ -54,5 +51,15 @@ namespace Bit.App.Controls
|
||||
{
|
||||
Picker.Focus();
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
Tapped += FormPickerCell_Tapped;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tapped -= FormPickerCell_Tapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Xamarin.Forms;
|
||||
using FFImageLoading.Forms;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
@@ -19,6 +20,14 @@ namespace Bit.App.Controls
|
||||
Style = (Style)Application.Current.Resources["text-muted"]
|
||||
};
|
||||
|
||||
LabelIcon = new CachedImage
|
||||
{
|
||||
WidthRequest = 16,
|
||||
HeightRequest = 16,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
Margin = new Thickness(5, 0, 0, 0)
|
||||
};
|
||||
|
||||
Button = new ExtendedButton
|
||||
{
|
||||
WidthRequest = 60
|
||||
@@ -32,11 +41,14 @@ 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.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(Button, 1, 0);
|
||||
grid.Children.Add(LabelIcon, 1, 0);
|
||||
grid.Children.Add(Button, 2, 0);
|
||||
Grid.SetColumnSpan(Detail, 2);
|
||||
Grid.SetRowSpan(Button, 2);
|
||||
|
||||
if(Device.OS == TargetPlatform.Android)
|
||||
@@ -49,6 +61,7 @@ namespace Bit.App.Controls
|
||||
|
||||
public Label Label { get; private set; }
|
||||
public Label Detail { get; private set; }
|
||||
public CachedImage LabelIcon { get; private set; }
|
||||
public Button Button { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
108
src/App/Controls/MemoryContentView.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Bit.App.Abstractions;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class MemoryContentView : ContentView
|
||||
{
|
||||
private readonly IMemoryService _memoryService;
|
||||
|
||||
public MemoryContentView()
|
||||
{
|
||||
_memoryService = Resolver.Resolve<IMemoryService>();
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
Padding = 5,
|
||||
BackgroundColor = Color.White,
|
||||
RowDefinitions = new RowDefinitionCollection
|
||||
{
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto }
|
||||
},
|
||||
ColumnDefinitions = new ColumnDefinitionCollection
|
||||
{
|
||||
new ColumnDefinition { Width = GridLength.Star },
|
||||
new ColumnDefinition { Width = GridLength.Star }
|
||||
}
|
||||
};
|
||||
|
||||
grid.Children.Add(new Label { Text = "Used Memory:" }, 0, 0);
|
||||
grid.Children.Add(new Label { Text = "Free Memory:" }, 0, 1);
|
||||
grid.Children.Add(new Label { Text = "Heap Memory:" }, 0, 2);
|
||||
grid.Children.Add(new Label { Text = "Max Memory:" }, 0, 3);
|
||||
grid.Children.Add(new Label { Text = "% Used Heap:" }, 0, 4);
|
||||
grid.Children.Add(new Label { Text = "% Used Max:" }, 0, 5);
|
||||
|
||||
UsedMemory = new Label { Text = "Used Memory:", HorizontalTextAlignment = TextAlignment.End };
|
||||
FreeMemory = new Label { Text = "Free Memory:", HorizontalTextAlignment = TextAlignment.End };
|
||||
HeapMemory = new Label { Text = "Heap Memory:", HorizontalTextAlignment = TextAlignment.End };
|
||||
MaxMemory = new Label { Text = "Max Memory:", HorizontalTextAlignment = TextAlignment.End };
|
||||
HeapUsage = new Label { Text = "% Used Heap:", HorizontalTextAlignment = TextAlignment.End };
|
||||
TotalUsage = new Label { Text = "% Used Max:", HorizontalTextAlignment = TextAlignment.End };
|
||||
|
||||
grid.Children.Add(UsedMemory, 1, 0);
|
||||
grid.Children.Add(FreeMemory, 1, 1);
|
||||
grid.Children.Add(HeapMemory, 1, 2);
|
||||
grid.Children.Add(MaxMemory, 1, 3);
|
||||
grid.Children.Add(HeapUsage, 1, 4);
|
||||
grid.Children.Add(TotalUsage, 1, 5);
|
||||
|
||||
var button = new ExtendedButton { Text = "Refresh", BackgroundColor = Color.Transparent };
|
||||
button.Clicked += Button_Clicked;
|
||||
grid.Children.Add(button, 0, 6);
|
||||
Grid.SetColumnSpan(button, 2);
|
||||
|
||||
Content = grid;
|
||||
|
||||
RefreshScreen();
|
||||
}
|
||||
|
||||
private void Button_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
RefreshScreen();
|
||||
}
|
||||
|
||||
public Label UsedMemory { get; set; }
|
||||
public Label FreeMemory { get; set; }
|
||||
public Label HeapMemory { get; set; }
|
||||
public Label MaxMemory { get; set; }
|
||||
public Label HeapUsage { get; set; }
|
||||
public Label TotalUsage { get; set; }
|
||||
|
||||
void RefreshScreen()
|
||||
{
|
||||
UsedMemory.Text = FreeMemory.Text = HeapMemory.Text = MaxMemory.Text =
|
||||
HeapUsage.Text = TotalUsage.Text = string.Empty;
|
||||
|
||||
UsedMemory.TextColor = FreeMemory.TextColor = HeapMemory.TextColor =
|
||||
MaxMemory.TextColor = HeapUsage.TextColor = TotalUsage.TextColor = Color.Black;
|
||||
|
||||
if(_memoryService != null)
|
||||
{
|
||||
var info = _memoryService.GetInfo();
|
||||
if(info != null)
|
||||
{
|
||||
UsedMemory.Text = string.Format("{0:N} mb", Math.Round(info.UsedMemory / 1024 / 1024D, 2));
|
||||
FreeMemory.Text = string.Format("{0:N} mb", Math.Round(info.FreeMemory / 1024 / 1024D, 2));
|
||||
HeapMemory.Text = string.Format("{0:N} mb", Math.Round(info.TotalMemory / 1024 / 1024D, 2));
|
||||
MaxMemory.Text = string.Format("{0:N} mb", Math.Round(info.MaxMemory / 1024 / 1024D, 2));
|
||||
HeapUsage.Text = string.Format("{0:P}", info.HeapUsage());
|
||||
TotalUsage.Text = string.Format("{0:P}", info.Usage());
|
||||
|
||||
if(info.Usage() > 0.8)
|
||||
{
|
||||
FreeMemory.TextColor = UsedMemory.TextColor = TotalUsage.TextColor = Color.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ namespace Bit.App.Controls
|
||||
MaxLength = 4,
|
||||
Margin = new Thickness(0, int.MaxValue, 0, 0)
|
||||
};
|
||||
Entry.TextChanged += Entry_TextChanged;
|
||||
|
||||
if(Device.OS == TargetPlatform.Android)
|
||||
{
|
||||
@@ -30,6 +29,9 @@ namespace Bit.App.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public Label Label { get; set; }
|
||||
public ExtendedEntry Entry { get; set; }
|
||||
|
||||
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if(e.NewTextValue.Length >= 4)
|
||||
@@ -38,7 +40,14 @@ namespace Bit.App.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public Label Label { get; set; }
|
||||
public ExtendedEntry Entry { get; set; }
|
||||
public void InitEvents()
|
||||
{
|
||||
Entry.TextChanged += Entry_TextChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Entry.TextChanged -= Entry_TextChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ namespace Bit.App.Controls
|
||||
Value = value
|
||||
};
|
||||
|
||||
Stepper.ValueChanged += Stepper_ValueChanged;
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
@@ -56,13 +54,23 @@ namespace Bit.App.Controls
|
||||
View = stackLayout;
|
||||
}
|
||||
|
||||
public Label Label { get; private set; }
|
||||
public Label StepperValueLabel { get; private set; }
|
||||
public Stepper Stepper { get; private set; }
|
||||
|
||||
private void Stepper_ValueChanged(object sender, ValueChangedEventArgs e)
|
||||
{
|
||||
StepperValueLabel.Text = e.NewValue.ToString();
|
||||
}
|
||||
|
||||
public Label Label { get; private set; }
|
||||
public Label StepperValueLabel { get; private set; }
|
||||
public Stepper Stepper { get; private set; }
|
||||
public void InitEvents()
|
||||
{
|
||||
Stepper.ValueChanged += Stepper_ValueChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stepper.ValueChanged -= Stepper_ValueChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,23 +6,22 @@ namespace Bit.App.Controls
|
||||
{
|
||||
public class VaultListViewCell : LabeledDetailCell
|
||||
{
|
||||
private Action<VaultListPageModel.Login> _moreClickedAction;
|
||||
|
||||
public static readonly BindableProperty LoginParameterProperty = BindableProperty.Create(nameof(LoginParameter),
|
||||
typeof(VaultListPageModel.Login), typeof(VaultListViewCell), null);
|
||||
|
||||
public VaultListViewCell(Action<VaultListPageModel.Login> moreClickedAction)
|
||||
{
|
||||
_moreClickedAction = moreClickedAction;
|
||||
|
||||
SetBinding(LoginParameterProperty, new Binding("."));
|
||||
Label.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Name);
|
||||
Detail.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Username);
|
||||
Label.SetBinding<VaultListPageModel.Login>(Label.TextProperty, l => l.Name);
|
||||
Detail.SetBinding<VaultListPageModel.Login>(Label.TextProperty, l => l.Username);
|
||||
LabelIcon.SetBinding<VaultListPageModel.Login>(VisualElement.IsVisibleProperty, l => l.Shared);
|
||||
|
||||
Button.Image = "more";
|
||||
Button.Command = new Command(() => ShowMore());
|
||||
Button.Command = new Command(() => moreClickedAction?.Invoke(LoginParameter));
|
||||
Button.BackgroundColor = Color.Transparent;
|
||||
|
||||
LabelIcon.Source = "share";
|
||||
|
||||
BackgroundColor = Color.White;
|
||||
}
|
||||
|
||||
@@ -31,10 +30,5 @@ namespace Bit.App.Controls
|
||||
get { return GetValue(LoginParameterProperty) as VaultListPageModel.Login; }
|
||||
set { SetValue(LoginParameterProperty, value); }
|
||||
}
|
||||
|
||||
private void ShowMore()
|
||||
{
|
||||
_moreClickedAction?.Invoke(LoginParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
{
|
||||
public enum CipherType : short
|
||||
{
|
||||
Folder = 0,
|
||||
// Folder deprecated
|
||||
//Folder = 0,
|
||||
Login = 1
|
||||
}
|
||||
}
|
||||
|
||||
11
src/App/Enums/EncryptionType.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Bit.App.Enums
|
||||
{
|
||||
public enum EncryptionType : byte
|
||||
{
|
||||
AesCbc256_B64 = 0,
|
||||
AesCbc128_HmacSha256_B64 = 1,
|
||||
AesCbc256_HmacSha256_B64 = 2,
|
||||
Rsa2048_OaepSha256_B64 = 3,
|
||||
Rsa2048_OaepSha1_B64 = 4
|
||||
}
|
||||
}
|
||||
9
src/App/Enums/OrganizationUserStatusType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Bit.App.Enums
|
||||
{
|
||||
public enum OrganizationUserStatusType : byte
|
||||
{
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2
|
||||
}
|
||||
}
|
||||
9
src/App/Enums/OrganizationUserType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Bit.App.Enums
|
||||
{
|
||||
public enum OrganizationUserType : byte
|
||||
{
|
||||
Owner = 0,
|
||||
Admin = 1,
|
||||
User = 2
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,13 @@
|
||||
SyncCipherCreate = 1,
|
||||
SyncLoginDelete = 2,
|
||||
SyncFolderDelete = 3,
|
||||
SyncCiphers = 4
|
||||
SyncCiphers = 4,
|
||||
|
||||
SyncVault = 5,
|
||||
SyncOrgKeys = 6,
|
||||
SyncFolderCreate = 7,
|
||||
SyncFolderUpdate = 8,
|
||||
SyncCipherDelete = 9,
|
||||
SyncSettings = 10
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{
|
||||
public LoginRequest(Login login)
|
||||
{
|
||||
OrganizationId = login.OrganizationId;
|
||||
FolderId = login.FolderId;
|
||||
Name = login.Name?.EncryptedString;
|
||||
Uri = login.Uri?.EncryptedString;
|
||||
@@ -13,6 +14,7 @@
|
||||
Favorite = login.Favorite;
|
||||
}
|
||||
|
||||
public string OrganizationId { get; set; }
|
||||
public string FolderId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Uri { get; set; }
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Bit.App.Models.Api
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string FolderId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public CipherType Type { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public JObject Data { get; set; }
|
||||
|
||||
8
src/App/Models/Api/Response/KeysResponse.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
public class KeysResponse
|
||||
{
|
||||
public string PublicKey { get; set; }
|
||||
public string PrivateKey { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ namespace Bit.App.Models.Api
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string FolderId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string Username { get; set; }
|
||||
@@ -13,8 +15,5 @@ namespace Bit.App.Models.Api
|
||||
public string Notes { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
|
||||
// Expandables
|
||||
public FolderResponse Folder { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
14
src/App/Models/Api/Response/ProfileOrganizationResponse.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Bit.App.Enums;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
public class ProfileOrganizationResponseModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Key { get; set; }
|
||||
public OrganizationUserStatusType Status { get; set; }
|
||||
public OrganizationUserType Type { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Bit.App.Models.Api
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
public class ProfileResponse
|
||||
{
|
||||
@@ -8,5 +10,6 @@
|
||||
public string MasterPasswordHint { get; set; }
|
||||
public string Culture { get; set; }
|
||||
public bool TwoFactorEnabled { get; set; }
|
||||
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ namespace Bit.App.Models.Api
|
||||
[JsonProperty("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
public List<int> TwoFactorProviders { get; set; }
|
||||
public string PrivateKey { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Enums;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
@@ -10,15 +11,66 @@ namespace Bit.App.Models
|
||||
|
||||
public CipherString(string encryptedString)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(encryptedString) || !encryptedString.Contains("|"))
|
||||
if(string.IsNullOrWhiteSpace(encryptedString))
|
||||
{
|
||||
throw new ArgumentException(nameof(encryptedString));
|
||||
}
|
||||
|
||||
var headerPieces = encryptedString.Split('.');
|
||||
string[] encPieces;
|
||||
|
||||
EncryptionType encType;
|
||||
if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out encType))
|
||||
{
|
||||
EncryptionType = encType;
|
||||
encPieces = headerPieces[1].Split('|');
|
||||
}
|
||||
else if(headerPieces.Length == 1)
|
||||
{
|
||||
encPieces = headerPieces[0].Split('|');
|
||||
EncryptionType = encPieces.Length == 3 ? EncryptionType.AesCbc128_HmacSha256_B64 : EncryptionType.AesCbc256_B64;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Malformed header.");
|
||||
}
|
||||
|
||||
switch(EncryptionType)
|
||||
{
|
||||
case EncryptionType.AesCbc256_B64:
|
||||
if(encPieces.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Malformed encPieces.");
|
||||
}
|
||||
InitializationVector = encPieces[0];
|
||||
CipherText = encPieces[1];
|
||||
break;
|
||||
case EncryptionType.AesCbc128_HmacSha256_B64:
|
||||
case EncryptionType.AesCbc256_HmacSha256_B64:
|
||||
if(encPieces.Length != 3)
|
||||
{
|
||||
throw new ArgumentException("Malformed encPieces.");
|
||||
}
|
||||
InitializationVector = encPieces[0];
|
||||
CipherText = encPieces[1];
|
||||
Mac = encPieces[2];
|
||||
break;
|
||||
case EncryptionType.Rsa2048_OaepSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha1_B64:
|
||||
if(encPieces.Length != 1)
|
||||
{
|
||||
throw new ArgumentException("Malformed encPieces.");
|
||||
}
|
||||
CipherText = encPieces[0];
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unknown encType.");
|
||||
}
|
||||
|
||||
EncryptedString = encryptedString;
|
||||
}
|
||||
|
||||
public CipherString(string initializationVector, string cipherText, string mac = null)
|
||||
public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText, string mac = null)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(initializationVector))
|
||||
{
|
||||
@@ -30,40 +82,47 @@ namespace Bit.App.Models
|
||||
throw new ArgumentNullException(nameof(cipherText));
|
||||
}
|
||||
|
||||
EncryptionType = encryptionType;
|
||||
EncryptedString = string.Format("{0}|{1}", initializationVector, cipherText);
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(mac))
|
||||
{
|
||||
EncryptedString = string.Format("{0}|{1}", EncryptedString, mac);
|
||||
}
|
||||
}
|
||||
|
||||
public string EncryptedString { get; private set; }
|
||||
public string InitializationVector => EncryptedString?.Split('|')[0] ?? null;
|
||||
public string CipherText => EncryptedString?.Split('|')[1] ?? null;
|
||||
public string Mac
|
||||
{
|
||||
get
|
||||
if(EncryptionType != EncryptionType.AesCbc256_B64)
|
||||
{
|
||||
var pieces = EncryptedString?.Split('|') ?? new string[0];
|
||||
if(pieces.Length > 2)
|
||||
{
|
||||
return pieces[2];
|
||||
}
|
||||
|
||||
return null;
|
||||
EncryptedString = string.Format("{0}.{1}", (byte)EncryptionType, EncryptedString);
|
||||
}
|
||||
|
||||
CipherText = cipherText;
|
||||
InitializationVector = initializationVector;
|
||||
Mac = mac;
|
||||
}
|
||||
public byte[] InitializationVectorBytes => Convert.FromBase64String(InitializationVector);
|
||||
|
||||
public EncryptionType EncryptionType { get; private set; }
|
||||
public string EncryptedString { get; private set; }
|
||||
public string InitializationVector { get; private set; }
|
||||
public string CipherText { get; private set; }
|
||||
public string Mac { get; private set; }
|
||||
public byte[] InitializationVectorBytes => string.IsNullOrWhiteSpace(InitializationVector) ?
|
||||
null : Convert.FromBase64String(InitializationVector);
|
||||
public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
|
||||
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);
|
||||
|
||||
public string Decrypt()
|
||||
public string Decrypt(string orgId = null)
|
||||
{
|
||||
if(_decryptedValue == null)
|
||||
{
|
||||
var cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
_decryptedValue = cryptoService.Decrypt(this);
|
||||
if(!string.IsNullOrWhiteSpace(orgId))
|
||||
{
|
||||
_decryptedValue = cryptoService.Decrypt(this, cryptoService.GetOrgKey(orgId));
|
||||
}
|
||||
else
|
||||
{
|
||||
_decryptedValue = cryptoService.Decrypt(this);
|
||||
}
|
||||
}
|
||||
|
||||
return _decryptedValue;
|
||||
|
||||
@@ -26,21 +26,6 @@ namespace Bit.App.Models.Data
|
||||
RevisionDateTime = folder.RevisionDate;
|
||||
}
|
||||
|
||||
public FolderData(CipherResponse cipher, string userId)
|
||||
{
|
||||
if(cipher.Type != Enums.CipherType.Folder)
|
||||
{
|
||||
throw new ArgumentException(nameof(cipher.Type));
|
||||
}
|
||||
|
||||
var data = cipher.Data.ToObject<LoginDataModel>();
|
||||
|
||||
Id = cipher.Id;
|
||||
UserId = userId;
|
||||
Name = data.Name;
|
||||
RevisionDateTime = cipher.RevisionDate;
|
||||
}
|
||||
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; }
|
||||
[Indexed]
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Bit.App.Models.Data
|
||||
Id = login.Id;
|
||||
FolderId = login.FolderId;
|
||||
UserId = userId;
|
||||
OrganizationId = login.OrganizationId;
|
||||
Name = login.Name?.EncryptedString;
|
||||
Uri = login.Uri?.EncryptedString;
|
||||
Username = login.Username?.EncryptedString;
|
||||
@@ -29,6 +30,7 @@ namespace Bit.App.Models.Data
|
||||
Id = login.Id;
|
||||
FolderId = login.FolderId;
|
||||
UserId = userId;
|
||||
OrganizationId = login.OrganizationId;
|
||||
Name = login.Name;
|
||||
Uri = login.Uri;
|
||||
Username = login.Username;
|
||||
@@ -50,6 +52,7 @@ namespace Bit.App.Models.Data
|
||||
Id = cipher.Id;
|
||||
FolderId = cipher.FolderId;
|
||||
UserId = userId;
|
||||
OrganizationId = cipher.OrganizationId;
|
||||
Name = data.Name;
|
||||
Uri = data.Uri;
|
||||
Username = data.Username;
|
||||
@@ -64,6 +67,7 @@ namespace Bit.App.Models.Data
|
||||
public string FolderId { get; set; }
|
||||
[Indexed]
|
||||
public string UserId { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string Username { get; set; }
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Bit.App.Models
|
||||
public Login(LoginData data)
|
||||
{
|
||||
Id = data.Id;
|
||||
UserId = data.UserId;
|
||||
OrganizationId = data.OrganizationId;
|
||||
FolderId = data.FolderId;
|
||||
Name = data.Name != null ? new CipherString(data.Name) : null;
|
||||
Uri = data.Uri != null ? new CipherString(data.Uri) : null;
|
||||
@@ -23,6 +25,8 @@ namespace Bit.App.Models
|
||||
public Login(LoginResponse response)
|
||||
{
|
||||
Id = response.Id;
|
||||
UserId = response.UserId;
|
||||
OrganizationId = response.OrganizationId;
|
||||
FolderId = response.FolderId;
|
||||
Name = response.Name != null ? new CipherString(response.Name) : null;
|
||||
Uri = response.Uri != null ? new CipherString(response.Uri) : null;
|
||||
@@ -32,6 +36,8 @@ namespace Bit.App.Models
|
||||
Favorite = response.Favorite;
|
||||
}
|
||||
|
||||
public string UserId { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public string FolderId { get; set; }
|
||||
public CipherString Uri { get; set; }
|
||||
public CipherString Username { get; set; }
|
||||
|
||||
15
src/App/Models/LoginResult.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class LoginResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
public class FullLoginResult : LoginResult
|
||||
{
|
||||
public bool TwoFactorRequired { get; set; }
|
||||
public SymmetricCryptoKey Key { get; set; }
|
||||
public string MasterPasswordHash { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,19 @@ namespace Bit.App.Models.Page
|
||||
{
|
||||
public class Login
|
||||
{
|
||||
private string _baseDomain;
|
||||
|
||||
public Login(Models.Login login)
|
||||
{
|
||||
Id = login.Id;
|
||||
Shared = !string.IsNullOrWhiteSpace(login.OrganizationId);
|
||||
FolderId = login.FolderId;
|
||||
Name = login.Name?.Decrypt();
|
||||
Username = login.Username?.Decrypt() ?? " ";
|
||||
Password = new Lazy<string>(() => login.Password?.Decrypt());
|
||||
Uri = new Lazy<string>(() => login.Uri?.Decrypt());
|
||||
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));
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public bool Shared { get; set; }
|
||||
public string FolderId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Username { get; set; }
|
||||
@@ -28,6 +28,17 @@ namespace Bit.App.Models.Page
|
||||
public Lazy<string> Uri { get; set; }
|
||||
}
|
||||
|
||||
public class AutofillLogin : Login
|
||||
{
|
||||
public AutofillLogin(Models.Login login, bool fuzzy = false)
|
||||
: base(login)
|
||||
{
|
||||
Fuzzy = fuzzy;
|
||||
}
|
||||
|
||||
public bool Fuzzy { get; set; }
|
||||
}
|
||||
|
||||
public class Folder : List<Login>
|
||||
{
|
||||
public Folder(Models.Folder folder)
|
||||
@@ -44,5 +55,16 @@ namespace Bit.App.Models.Page
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; } = AppResources.FolderNone;
|
||||
}
|
||||
|
||||
public class AutofillGrouping : List<AutofillLogin>
|
||||
{
|
||||
public AutofillGrouping(List<AutofillLogin> logins, string name)
|
||||
{
|
||||
AddRange(logins);
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,26 +126,19 @@ namespace Bit.App.Models.Page
|
||||
return _uriHost;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var host = new Uri(Uri).Host;
|
||||
|
||||
DomainName domain;
|
||||
if(DomainName.TryParse(host, out domain))
|
||||
{
|
||||
_uriHost = domain.BaseDomain;
|
||||
}
|
||||
else
|
||||
{
|
||||
_uriHost = host;
|
||||
}
|
||||
|
||||
return _uriHost;
|
||||
}
|
||||
catch
|
||||
Uri uri;
|
||||
if(!System.Uri.TryCreate(Uri, UriKind.Absolute, out uri))
|
||||
{
|
||||
return Uri;
|
||||
}
|
||||
|
||||
DomainName domain;
|
||||
if(DomainName.TryParse(uri.Host, out domain))
|
||||
{
|
||||
return domain.BaseDomain;
|
||||
}
|
||||
|
||||
return uri.Host;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,11 +171,11 @@ namespace Bit.App.Models.Page
|
||||
|
||||
public void Update(Login login)
|
||||
{
|
||||
Name = login.Name?.Decrypt();
|
||||
Username = login.Username?.Decrypt();
|
||||
Password = login.Password?.Decrypt();
|
||||
Uri = login.Uri?.Decrypt();
|
||||
Notes = login.Notes?.Decrypt();
|
||||
Name = login.Name?.Decrypt(login.OrganizationId);
|
||||
Username = login.Username?.Decrypt(login.OrganizationId);
|
||||
Password = login.Password?.Decrypt(login.OrganizationId);
|
||||
Uri = login.Uri?.Decrypt(login.OrganizationId);
|
||||
Notes = login.Notes?.Decrypt(login.OrganizationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,24 @@ namespace Bit.App.Models
|
||||
public PushType Type { get; set; }
|
||||
}
|
||||
|
||||
public abstract class SyncPushNotification : PushNotification
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
|
||||
public class SyncCipherPushNotification : SyncPushNotification
|
||||
public class SyncCipherPushNotification : PushNotification
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
||||
|
||||
public class SyncCiphersPushNotification : SyncPushNotification
|
||||
public class SyncFolderPushNotification : PushNotification
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
||||
|
||||
public class SyncUserPushNotification : PushNotification
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
62
src/App/Models/SymmetricCryptoKey.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Bit.App.Enums;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class SymmetricCryptoKey
|
||||
{
|
||||
public SymmetricCryptoKey(byte[] rawBytes, EncryptionType? encType = null)
|
||||
{
|
||||
if(rawBytes == null || rawBytes.Length == 0)
|
||||
{
|
||||
throw new Exception("Must provide keyBytes.");
|
||||
}
|
||||
|
||||
if(encType == null)
|
||||
{
|
||||
if(rawBytes.Length == 32)
|
||||
{
|
||||
encType = EncryptionType.AesCbc256_B64;
|
||||
}
|
||||
else if(rawBytes.Length == 64)
|
||||
{
|
||||
encType = EncryptionType.AesCbc256_HmacSha256_B64;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unable to determine encType.");
|
||||
}
|
||||
}
|
||||
|
||||
EncryptionType = encType.Value;
|
||||
Key = rawBytes;
|
||||
|
||||
if(EncryptionType == EncryptionType.AesCbc256_B64 && Key.Length == 32)
|
||||
{
|
||||
EncKey = Key;
|
||||
MacKey = null;
|
||||
}
|
||||
else if(EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
|
||||
{
|
||||
EncKey = Key.Take(16).ToArray();
|
||||
MacKey = Key.Skip(16).Take(16).ToArray();
|
||||
}
|
||||
else if(EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
|
||||
{
|
||||
EncKey = Key.Take(32).ToArray();
|
||||
MacKey = Key.Skip(32).Take(32).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unsupported encType/key length.");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Key { get; set; }
|
||||
public string B64Key => Convert.ToBase64String(Key);
|
||||
public byte[] EncKey { get; set; }
|
||||
public byte[] MacKey { get; set; }
|
||||
public EncryptionType EncryptionType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -30,11 +31,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
|
||||
var logo = new Image
|
||||
var logo = new CachedImage
|
||||
{
|
||||
Source = "logo",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 282,
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var message = new Label
|
||||
|
||||
@@ -6,6 +6,7 @@ using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Fingerprint.Abstractions;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -13,6 +14,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly IFingerprint _fingerprint;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppSettingsService _appSettings;
|
||||
private readonly bool _checkFingerprintImmediately;
|
||||
|
||||
public LockFingerprintPage(bool checkFingerprintImmediately)
|
||||
@@ -20,6 +22,7 @@ namespace Bit.App.Pages
|
||||
_checkFingerprintImmediately = checkFingerprintImmediately;
|
||||
_fingerprint = Resolver.Resolve<IFingerprint>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
@@ -79,7 +82,7 @@ namespace Bit.App.Pages
|
||||
var result = await _fingerprint.AuthenticateAsync(AppResources.FingerprintDirection);
|
||||
if(result.Authenticated)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.Locked, false);
|
||||
_appSettings.Locked = false;
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
|
||||
|
||||
@@ -6,20 +6,19 @@ using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Controls;
|
||||
using System.Linq;
|
||||
using Plugin.Settings.Abstractions;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LockPasswordPage : BaseLockPage
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
|
||||
public LockPasswordPage()
|
||||
{
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
_cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
|
||||
Init();
|
||||
@@ -38,7 +37,6 @@ namespace Bit.App.Pages
|
||||
useLabelAsPlaceholder: true, imageSource: "lock", containerPadding: padding);
|
||||
|
||||
PasswordCell.Entry.ReturnType = Enums.ReturnType.Go;
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -50,7 +48,7 @@ namespace Bit.App.Pages
|
||||
NoFooter = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
PasswordCell
|
||||
}
|
||||
@@ -99,7 +97,16 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
PasswordCell.InitEvents();
|
||||
PasswordCell.Entry.FocusWithDelay();
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PasswordCell.Dispose();
|
||||
PasswordCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
protected async Task CheckPasswordAsync()
|
||||
@@ -112,9 +119,9 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, _authService.Email);
|
||||
if(key.SequenceEqual(_cryptoService.Key))
|
||||
if(key.Key.SequenceEqual(_cryptoService.Key.Key))
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.Locked, false);
|
||||
_appSettingsService.Locked = false;
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Acr.UserDialogs;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Controls;
|
||||
|
||||
@@ -14,12 +11,13 @@ namespace Bit.App.Pages
|
||||
public class LockPinPage : BaseLockPage
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppSettingsService _appSettingsService;
|
||||
private TapGestureRecognizer _tgr;
|
||||
|
||||
public LockPinPage()
|
||||
{
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
@@ -39,7 +37,6 @@ namespace Bit.App.Pages
|
||||
};
|
||||
|
||||
PinControl = new PinControl();
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.Label.SetBinding<PinPageModel>(Label.TextProperty, s => s.LabelText);
|
||||
PinControl.Entry.SetBinding<PinPageModel>(Entry.TextProperty, s => s.PIN);
|
||||
|
||||
@@ -60,14 +57,13 @@ namespace Bit.App.Pages
|
||||
Children = { PinControl.Label, instructionLabel, logoutButton, PinControl.Entry }
|
||||
};
|
||||
|
||||
var tgr = new TapGestureRecognizer();
|
||||
tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.Label.GestureRecognizers.Add(tgr);
|
||||
instructionLabel.GestureRecognizers.Add(tgr);
|
||||
_tgr = new TapGestureRecognizer();
|
||||
PinControl.Label.GestureRecognizers.Add(_tgr);
|
||||
instructionLabel.GestureRecognizers.Add(_tgr);
|
||||
|
||||
Title = AppResources.VerifyPIN;
|
||||
Content = stackLayout;
|
||||
Content.GestureRecognizers.Add(tgr);
|
||||
Content.GestureRecognizers.Add(_tgr);
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
@@ -79,14 +75,25 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
_tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.InitEvents();
|
||||
PinControl.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
_tgr.Tapped -= Tgr_Tapped;
|
||||
PinControl.OnPinEntered -= PinEntered;
|
||||
PinControl.Dispose();
|
||||
}
|
||||
|
||||
protected void PinEntered(object sender, EventArgs args)
|
||||
{
|
||||
if(Model.PIN == _authService.PIN)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.Locked, false);
|
||||
_appSettingsService.Locked = false;
|
||||
PinControl.Entry.Unfocus();
|
||||
Navigation.PopModalAsync();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Api;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
@@ -15,11 +13,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
public class LoginPage : ExtendedContentPage
|
||||
{
|
||||
private ICryptoService _cryptoService;
|
||||
private IAuthService _authService;
|
||||
private ITokenService _tokenService;
|
||||
private IDeviceInfoService _deviceInfoService;
|
||||
private IAppIdService _appIdService;
|
||||
private IUserDialogs _userDialogs;
|
||||
private ISyncService _syncService;
|
||||
private ISettings _settings;
|
||||
@@ -31,11 +25,7 @@ namespace Bit.App.Pages
|
||||
: base(updateActivity: false)
|
||||
{
|
||||
_email = email;
|
||||
_cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_tokenService = Resolver.Resolve<ITokenService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_appIdService = Resolver.Resolve<IAppIdService>();
|
||||
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
@@ -74,7 +64,6 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
PasswordCell.Entry.ReturnType = Enums.ReturnType.Go;
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -83,10 +72,11 @@ namespace Bit.App.Pages
|
||||
HasUnevenRows = true,
|
||||
EnableSelection = true,
|
||||
NoFooter = true,
|
||||
//NoHeader = true,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection()
|
||||
new TableSection(" ")
|
||||
{
|
||||
EmailCell,
|
||||
PasswordCell
|
||||
@@ -136,6 +126,10 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
PasswordCell.InitEvents();
|
||||
EmailCell.InitEvents();
|
||||
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
|
||||
if(string.IsNullOrWhiteSpace(_email))
|
||||
@@ -151,6 +145,14 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PasswordCell.Dispose();
|
||||
EmailCell.Dispose();
|
||||
PasswordCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await LogIn();
|
||||
@@ -177,40 +179,22 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedEmail = EmailCell.Entry.Text.ToLower();
|
||||
|
||||
var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, normalizedEmail);
|
||||
|
||||
var request = new TokenRequest
|
||||
{
|
||||
Email = normalizedEmail,
|
||||
MasterPasswordHash = _cryptoService.HashPasswordBase64(key, PasswordCell.Entry.Text),
|
||||
Device = new DeviceRequest(_appIdService, _deviceInfoService)
|
||||
};
|
||||
|
||||
_userDialogs.ShowLoading(AppResources.LoggingIn, MaskType.Black);
|
||||
var response = await _authService.TokenPostAsync(request);
|
||||
var result = await _authService.TokenPostAsync(EmailCell.Entry.Text, PasswordCell.Entry.Text);
|
||||
_userDialogs.HideLoading();
|
||||
if(!response.Succeeded)
|
||||
if(!result.Success)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok);
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, result.ErrorMessage, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if(response.Result.TwoFactorProviders != null && response.Result.TwoFactorProviders.Count > 0)
|
||||
if(result.TwoFactorRequired)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedIn To Two-step");
|
||||
await Navigation.PushAsync(new LoginTwoFactorPage(request.Email, request.MasterPasswordHash, key));
|
||||
await Navigation.PushAsync(new LoginTwoFactorPage(EmailCell.Entry.Text, result.MasterPasswordHash, result.Key));
|
||||
return;
|
||||
}
|
||||
|
||||
_cryptoService.Key = key;
|
||||
_tokenService.Token = response.Result.AccessToken;
|
||||
_tokenService.RefreshToken = response.Result.RefreshToken;
|
||||
_authService.UserId = _tokenService.TokenUserId;
|
||||
_authService.Email = _tokenService.TokenEmail;
|
||||
_settings.AddOrUpdateValue(Constants.LastLoginEmail, _authService.Email);
|
||||
_googleAnalyticsService.RefreshUserId();
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedIn");
|
||||
|
||||
if(Device.OS == TargetPlatform.Android)
|
||||
|
||||
@@ -1,49 +1,37 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Api;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Acr.UserDialogs;
|
||||
using System.Threading.Tasks;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using PushNotification.Plugin.Abstractions;
|
||||
using Bit.App.Models;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LoginTwoFactorPage : ExtendedContentPage
|
||||
{
|
||||
private ICryptoService _cryptoService;
|
||||
private IAuthService _authService;
|
||||
private ITokenService _tokenService;
|
||||
private IDeviceInfoService _deviceInfoService;
|
||||
private IAppIdService _appIdService;
|
||||
private IUserDialogs _userDialogs;
|
||||
private ISyncService _syncService;
|
||||
private ISettings _settings;
|
||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private IPushNotification _pushNotification;
|
||||
private readonly string _email;
|
||||
private readonly string _masterPasswordHash;
|
||||
private readonly byte[] _key;
|
||||
private readonly SymmetricCryptoKey _key;
|
||||
|
||||
public LoginTwoFactorPage(string email, string masterPasswordHash, byte[] key)
|
||||
public LoginTwoFactorPage(string email, string masterPasswordHash, SymmetricCryptoKey key)
|
||||
: base(updateActivity: false)
|
||||
{
|
||||
_email = email;
|
||||
_masterPasswordHash = masterPasswordHash;
|
||||
_key = key;
|
||||
|
||||
_cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_tokenService = Resolver.Resolve<ITokenService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_appIdService = Resolver.Resolve<IAppIdService>();
|
||||
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_pushNotification = Resolver.Resolve<IPushNotification>();
|
||||
|
||||
@@ -64,7 +52,6 @@ namespace Bit.App.Pages
|
||||
|
||||
CodeCell.Entry.Keyboard = Keyboard.Numeric;
|
||||
CodeCell.Entry.ReturnType = Enums.ReturnType.Go;
|
||||
CodeCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -76,7 +63,7 @@ namespace Bit.App.Pages
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection()
|
||||
new TableSection(" ")
|
||||
{
|
||||
CodeCell
|
||||
}
|
||||
@@ -118,7 +105,7 @@ namespace Bit.App.Pages
|
||||
|
||||
var continueToolbarItem = new ToolbarItem(AppResources.Continue, null, async () =>
|
||||
{
|
||||
await LogIn();
|
||||
await LogInAsync();
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
ToolbarItems.Add(continueToolbarItem);
|
||||
@@ -129,7 +116,16 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
CodeCell.InitEvents();
|
||||
CodeCell.Entry.FocusWithDelay();
|
||||
CodeCell.Entry.Completed += Entry_Completed;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
CodeCell.Dispose();
|
||||
CodeCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private void Lost2FAApp()
|
||||
@@ -139,10 +135,10 @@ namespace Bit.App.Pages
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await LogIn();
|
||||
await LogInAsync();
|
||||
}
|
||||
|
||||
private async Task LogIn()
|
||||
private async Task LogInAsync()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(CodeCell.Entry.Text))
|
||||
{
|
||||
@@ -151,31 +147,15 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new TokenRequest
|
||||
{
|
||||
Email = _email,
|
||||
MasterPasswordHash = _masterPasswordHash,
|
||||
Token = CodeCell.Entry.Text.Replace(" ", ""),
|
||||
Provider = 0, // Authenticator app (only 1 provider for now, so hard coded)
|
||||
Device = new DeviceRequest(_appIdService, _deviceInfoService)
|
||||
};
|
||||
|
||||
_userDialogs.ShowLoading(AppResources.ValidatingCode, MaskType.Black);
|
||||
var response = await _authService.TokenPostAsync(request);
|
||||
var response = await _authService.TokenPostTwoFactorAsync(CodeCell.Entry.Text, _email, _masterPasswordHash, _key);
|
||||
_userDialogs.HideLoading();
|
||||
if(!response.Succeeded)
|
||||
if(!response.Success)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, response.Errors.FirstOrDefault()?.Message, AppResources.Ok);
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, response.ErrorMessage, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
_cryptoService.Key = _key;
|
||||
_tokenService.Token = response.Result.AccessToken;
|
||||
_tokenService.RefreshToken = response.Result.RefreshToken;
|
||||
_authService.UserId = _tokenService.TokenUserId;
|
||||
_authService.Email = _tokenService.TokenEmail;
|
||||
_settings.AddOrUpdateValue(Constants.LastLoginEmail, _authService.Email);
|
||||
_googleAnalyticsService.RefreshUserId();
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedIn From Two-step");
|
||||
|
||||
if(Device.OS == TargetPlatform.Android)
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
public class MainPage : ExtendedTabbedPage
|
||||
{
|
||||
public MainPage()
|
||||
public MainPage(string uri = null)
|
||||
{
|
||||
TintColor = Color.FromHex("3c8dbc");
|
||||
|
||||
var settingsNavigation = new ExtendedNavigationPage(new SettingsPage());
|
||||
var favoritesNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(true));
|
||||
var vaultNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(false));
|
||||
var favoritesNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(true, uri));
|
||||
var vaultNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(false, uri));
|
||||
var toolsNavigation = new ExtendedNavigationPage(new ToolsPage());
|
||||
|
||||
favoritesNavigation.Title = AppResources.Favorites;
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace Bit.App.Pages
|
||||
useLabelAsPlaceholder: true, imageSource: "envelope", containerPadding: padding);
|
||||
|
||||
EmailCell.Entry.ReturnType = Enums.ReturnType.Go;
|
||||
EmailCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -50,7 +49,7 @@ namespace Bit.App.Pages
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection()
|
||||
new TableSection(" ")
|
||||
{
|
||||
EmailCell
|
||||
}
|
||||
@@ -93,9 +92,18 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
EmailCell.InitEvents();
|
||||
EmailCell.Entry.Completed += Entry_Completed;
|
||||
EmailCell.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Dispose();
|
||||
EmailCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await SubmitAsync();
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace Bit.App.Pages
|
||||
public FormEntryCell PasswordCell { get; set; }
|
||||
public FormEntryCell ConfirmPasswordCell { get; set; }
|
||||
public FormEntryCell PasswordHintCell { get; set; }
|
||||
public StackLayout StackLayout { get; set; }
|
||||
public Label PasswordLabel { get; set; }
|
||||
public Label HintLabel { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
@@ -58,13 +61,12 @@ namespace Bit.App.Pages
|
||||
containerPadding: padding);
|
||||
|
||||
PasswordHintCell.Entry.ReturnType = Enums.ReturnType.Done;
|
||||
PasswordHintCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new FormTableView
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
EmailCell,
|
||||
PasswordCell
|
||||
@@ -72,7 +74,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
};
|
||||
|
||||
var passwordLabel = new Label
|
||||
PasswordLabel = new Label
|
||||
{
|
||||
Text = AppResources.MasterPasswordDescription,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
@@ -86,7 +88,7 @@ namespace Bit.App.Pages
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
ConfirmPasswordCell,
|
||||
PasswordHintCell
|
||||
@@ -94,7 +96,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
};
|
||||
|
||||
var hintLabel = new Label
|
||||
HintLabel = new Label
|
||||
{
|
||||
Text = AppResources.MasterPasswordHintDescription,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
@@ -103,21 +105,15 @@ namespace Bit.App.Pages
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
|
||||
};
|
||||
|
||||
var layout = new StackLayout
|
||||
StackLayout = new StackLayout
|
||||
{
|
||||
Children = { table, passwordLabel, table2, hintLabel },
|
||||
Children = { table, PasswordLabel, table2, HintLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
layout.LayoutChanged += (sender, args) =>
|
||||
{
|
||||
passwordLabel.WidthRequest = layout.Bounds.Width - passwordLabel.Bounds.Left * 2;
|
||||
hintLabel.WidthRequest = layout.Bounds.Width - hintLabel.Bounds.Left * 2;
|
||||
};
|
||||
|
||||
var scrollView = new ScrollView
|
||||
{
|
||||
Content = layout
|
||||
Content = StackLayout
|
||||
};
|
||||
|
||||
var loginToolbarItem = new ToolbarItem(AppResources.Submit, null, async () =>
|
||||
@@ -144,8 +140,30 @@ namespace Bit.App.Pages
|
||||
{
|
||||
base.OnAppearing();
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
EmailCell.InitEvents();
|
||||
PasswordCell.InitEvents();
|
||||
PasswordHintCell.InitEvents();
|
||||
ConfirmPasswordCell.InitEvents();
|
||||
PasswordHintCell.Entry.Completed += Entry_Completed;
|
||||
StackLayout.LayoutChanged += Layout_LayoutChanged;
|
||||
EmailCell.Entry.FocusWithDelay();
|
||||
}
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Dispose();
|
||||
PasswordCell.Dispose();
|
||||
PasswordHintCell.Dispose();
|
||||
ConfirmPasswordCell.Dispose();
|
||||
PasswordHintCell.Entry.Completed -= Entry_Completed;
|
||||
StackLayout.LayoutChanged -= Layout_LayoutChanged;
|
||||
}
|
||||
|
||||
private void Layout_LayoutChanged(object sender, EventArgs e)
|
||||
{
|
||||
PasswordLabel.WidthRequest = StackLayout.Bounds.Width - PasswordLabel.Bounds.Left * 2;
|
||||
HintLabel.WidthRequest = StackLayout.Bounds.Width - HintLabel.Bounds.Left * 2;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using Xamarin.Forms;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -17,12 +18,16 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedTextCell CreditsCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var logo = new Image
|
||||
var logo = new CachedImage
|
||||
{
|
||||
Source = "logo",
|
||||
HorizontalOptions = LayoutOptions.Center
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 282,
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var versionLabel = new Label
|
||||
@@ -40,12 +45,11 @@ namespace Bit.App.Pages
|
||||
Padding = new Thickness(0, 40, 0, 0)
|
||||
};
|
||||
|
||||
var creditsCell = new ExtendedTextCell
|
||||
CreditsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Credits,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
creditsCell.Tapped += RateCell_Tapped;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -55,9 +59,9 @@ namespace Bit.App.Pages
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
creditsCell
|
||||
CreditsCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -87,5 +91,17 @@ namespace Bit.App.Pages
|
||||
{
|
||||
Navigation.PushAsync(new SettingsCreditsPage());
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
CreditsCell.Tapped += RateCell_Tapped;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
CreditsCell.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell NameCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var nameCell = new FormEntryCell(AppResources.Name);
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -39,9 +41,9 @@ namespace Bit.App.Pages
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection()
|
||||
new TableSection(" ")
|
||||
{
|
||||
nameCell
|
||||
NameCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -60,7 +62,7 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(nameCell.Entry.Text))
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
@@ -69,7 +71,7 @@ namespace Bit.App.Pages
|
||||
|
||||
var folder = new Folder
|
||||
{
|
||||
Name = nameCell.Entry.Text.Encrypt()
|
||||
Name = NameCell.Entry.Text.Encrypt()
|
||||
};
|
||||
|
||||
_userDialogs.ShowLoading(AppResources.Saving, MaskType.Black);
|
||||
@@ -105,12 +107,19 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell.InitEvents();
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
NameCell.Dispose();
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
|
||||
|
||||
@@ -25,8 +25,9 @@ namespace Bit.App.Pages
|
||||
new TableSection(AppResources.Translations)
|
||||
{
|
||||
new CustomViewCell(@"@felixqu - Chinese
|
||||
@Primokorn - French
|
||||
@King-Tut-Tut - Swedish")
|
||||
@Primokorn, @maxlandry - French
|
||||
@King-Tut-Tut - Swedish
|
||||
@Igetin - Finnish")
|
||||
},
|
||||
new TableSection(AppResources.Icons)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell NameCell { get; set; }
|
||||
public ExtendedTextCell DeleteCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var folder = _folderService.GetByIdAsync(_folderId).GetAwaiter().GetResult();
|
||||
@@ -38,11 +41,10 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
var nameCell = new FormEntryCell(AppResources.Name);
|
||||
nameCell.Entry.Text = folder.Name.Decrypt();
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
NameCell.Entry.Text = folder.Name.Decrypt();
|
||||
|
||||
var deleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
deleteCell.Tapped += DeleteCell_Tapped;
|
||||
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
|
||||
var mainTable = new ExtendedTableView
|
||||
{
|
||||
@@ -52,13 +54,13 @@ namespace Bit.App.Pages
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
nameCell
|
||||
NameCell
|
||||
},
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
deleteCell
|
||||
DeleteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -77,14 +79,14 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(nameCell.Entry.Text))
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
folder.Name = nameCell.Entry.Text.Encrypt();
|
||||
folder.Name = NameCell.Entry.Text.Encrypt();
|
||||
|
||||
_userDialogs.ShowLoading(AppResources.Saving, MaskType.Black);
|
||||
var saveResult = await _folderService.SaveAsync(folder);
|
||||
@@ -119,12 +121,22 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell.InitEvents();
|
||||
DeleteCell.Tapped += DeleteCell_Tapped;
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
NameCell.Dispose();
|
||||
DeleteCell.Tapped -= DeleteCell_Tapped;
|
||||
}
|
||||
|
||||
private async void DeleteCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
@@ -140,7 +152,6 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_userDialogs.ShowLoading(AppResources.Deleting, MaskType.Black);
|
||||
var deleteTask = await _folderService.DeleteAsync(_folderId);
|
||||
_userDialogs.HideLoading();
|
||||
|
||||
@@ -18,99 +18,122 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedTextCell EmailCell { get; set; }
|
||||
public ExtendedTextCell WebsiteCell { get; set; }
|
||||
public ExtendedTextCell BugCell { get; set; }
|
||||
public StackLayout StackLayout { get; set; }
|
||||
private CustomLabel EmailLabel { get; set; }
|
||||
private CustomLabel WebsiteLabel { get; set; }
|
||||
private CustomLabel BugLabel { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var emailCell = new ExtendedTextCell
|
||||
EmailCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.EmailUs,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
emailCell.Tapped += EmailCell_Tapped;
|
||||
|
||||
var emailTable = new CustomTableView
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
emailCell
|
||||
EmailCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var emailLabel = new CustomLabel(this)
|
||||
EmailLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.EmailUsDescription
|
||||
};
|
||||
|
||||
var websiteCell = new ExtendedTextCell
|
||||
WebsiteCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.VisitOurWebsite,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
websiteCell.Tapped += WebsiteCell_Tapped;
|
||||
|
||||
var websiteTable = new CustomTableView
|
||||
{
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
websiteCell
|
||||
WebsiteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var websiteLabel = new CustomLabel(this)
|
||||
WebsiteLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.VisitOurWebsiteDescription
|
||||
};
|
||||
|
||||
var bugCell = new ExtendedTextCell
|
||||
BugCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.FileBugReport,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
bugCell.Tapped += BugCell_Tapped;
|
||||
|
||||
var bugTable = new CustomTableView
|
||||
{
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection
|
||||
new TableSection(" ")
|
||||
{
|
||||
bugCell
|
||||
BugCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var bugLabel = new CustomLabel(this)
|
||||
BugLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.FileBugReportDescription
|
||||
};
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
StackLayout = new StackLayout
|
||||
{
|
||||
Children = { emailTable, emailLabel, websiteTable, websiteLabel, bugTable, bugLabel },
|
||||
Children = { emailTable, EmailLabel, websiteTable, WebsiteLabel, bugTable, BugLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
stackLayout.LayoutChanged += (sender, args) =>
|
||||
{
|
||||
websiteLabel.WidthRequest = stackLayout.Bounds.Width - websiteLabel.Bounds.Left * 2;
|
||||
emailLabel.WidthRequest = stackLayout.Bounds.Width - emailLabel.Bounds.Left * 2;
|
||||
bugLabel.WidthRequest = stackLayout.Bounds.Width - bugLabel.Bounds.Left * 2;
|
||||
};
|
||||
|
||||
if(Device.OS == TargetPlatform.iOS)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
|
||||
Title = AppResources.HelpAndFeedback;
|
||||
Content = new ScrollView { Content = stackLayout };
|
||||
Content = new ScrollView { Content = StackLayout };
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
EmailCell.Tapped += EmailCell_Tapped;
|
||||
WebsiteCell.Tapped += WebsiteCell_Tapped;
|
||||
BugCell.Tapped += BugCell_Tapped;
|
||||
StackLayout.LayoutChanged += StackLayout_LayoutChanged;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Tapped -= EmailCell_Tapped;
|
||||
WebsiteCell.Tapped -= WebsiteCell_Tapped;
|
||||
BugCell.Tapped -= BugCell_Tapped;
|
||||
StackLayout.LayoutChanged -= StackLayout_LayoutChanged;
|
||||
}
|
||||
|
||||
private void StackLayout_LayoutChanged(object sender, EventArgs e)
|
||||
{
|
||||
WebsiteLabel.WidthRequest = StackLayout.Bounds.Width - WebsiteLabel.Bounds.Left * 2;
|
||||
EmailLabel.WidthRequest = StackLayout.Bounds.Width - EmailLabel.Bounds.Left * 2;
|
||||
BugLabel.WidthRequest = StackLayout.Bounds.Width - BugLabel.Bounds.Left * 2;
|
||||
}
|
||||
|
||||
private void EmailCell_Tapped(object sender, EventArgs e)
|
||||
|
||||