diff --git a/.github/resources/export-options-app-store.plist b/.github/resources/export-options-app-store.plist
index 92b07238b..df3ed635b 100644
--- a/.github/resources/export-options-app-store.plist
+++ b/.github/resources/export-options-app-store.plist
@@ -12,6 +12,8 @@
Dist: Autofill 2021
com.8bit.bitwarden.find-login-action-extension
Dist: Extension 2021
+ com.8bit.bitwarden.share-extension
+ Dist: Share Extension 2021
diff --git a/.github/secrets/dist_share_extension.mobileprovision.gpg b/.github/secrets/dist_share_extension.mobileprovision.gpg
new file mode 100644
index 000000000..0fad3826d
Binary files /dev/null and b/.github/secrets/dist_share_extension.mobileprovision.gpg differ
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7650fadca..7578e3849 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -382,6 +382,8 @@ jobs:
--output $HOME/secrets/dist_bitwarden.mobileprovision ./.github/secrets/dist_bitwarden.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
+ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
+ --output $HOME/secrets/dist_share_extension.mobileprovision ./.github/secrets/dist_share_extension.mobileprovision.gpg
shell: bash
- name: Increment version
@@ -395,6 +397,16 @@ jobs:
perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS/Info.plist
perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
+ perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
+ shell: bash
+
+ - name: Update Entitlements
+ run: |
+ echo "########################################"
+ echo "##### Updating Entitlements"
+ echo "########################################"
+
+ perl -0777 -pi.bak -e 's/aps-environment<\/key>\s*development<\/string>/aps-environment<\/key>\n\tproduction<\/string>/' ./src/iOS/Entitlements.plist
shell: bash
- name: Set up Keychain
@@ -419,6 +431,7 @@ jobs:
AUTOFILL_PROFILE_PATH=$HOME/secrets/dist_autofill.mobileprovision
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
+ SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH"
@@ -431,6 +444,9 @@ jobs:
EXTENSION_UUID=$(grep UUID -A1 -a $EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$EXTENSION_UUID.mobileprovision"
+
+ SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
+ cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision"
shell: bash
- name: Restore packages
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index bd4e02855..66dfb66c9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -35,7 +35,7 @@ jobs:
- name: Retrieve Mobile release version
id: retrieve-mobile-version
run: |
- ver=$(sed -n -e '/android:versionName/ s/.*\= *//p' ./src/Android/Properties/AndroidManifest.xml | tr -d '"')
+ ver=$(sed -E -n '/^
-
+
@@ -182,15 +182,16 @@
-
+
-
+
+
diff --git a/src/Android/Assets/FontAwesome.ttf b/src/Android/Assets/FontAwesome.ttf
deleted file mode 100644
index 35acda2fa..000000000
Binary files a/src/Android/Assets/FontAwesome.ttf and /dev/null differ
diff --git a/src/Android/Assets/bwi-font.ttf b/src/Android/Assets/bwi-font.ttf
new file mode 100644
index 000000000..358fca186
Binary files /dev/null and b/src/Android/Assets/bwi-font.ttf differ
diff --git a/src/Android/Autofill/AutofillHelpers.cs b/src/Android/Autofill/AutofillHelpers.cs
index cd36b553d..ab10aa005 100644
--- a/src/Android/Autofill/AutofillHelpers.cs
+++ b/src/Android/Autofill/AutofillHelpers.cs
@@ -73,6 +73,7 @@ namespace Bit.Droid.Autofill
"com.google.android.captiveportallogin",
"com.jamal2367.styx",
"com.kiwibrowser.browser",
+ "com.kiwibrowser.browser.dev",
"com.microsoft.emmx",
"com.microsoft.emmx.beta",
"com.microsoft.emmx.canary",
@@ -159,7 +160,7 @@ namespace Bit.Droid.Autofill
return new List();
}
- public static FillResponse BuildFillResponse(Parser parser, List items, bool locked,
+ public static FillResponse.Builder CreateFillResponse(Parser parser, List items, bool locked,
bool inlineAutofillEnabled, FillRequest fillRequest = null)
{
// Acquire inline presentation specs on Android 11+
@@ -210,9 +211,8 @@ namespace Bit.Droid.Autofill
}
responseBuilder.AddDataset(BuildVaultDataset(parser.ApplicationContext, parser.FieldCollection,
parser.Uri, locked, inlinePresentationSpecs));
- AddSaveInfo(parser, fillRequest, responseBuilder, parser.FieldCollection);
responseBuilder.SetIgnoredIds(parser.FieldCollection.IgnoreAutofillIds.ToArray());
- return responseBuilder.Build();
+ return responseBuilder;
}
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs
index 38ecbe4a9..0d0b1cc07 100644
--- a/src/Android/Autofill/AutofillService.cs
+++ b/src/Android/Autofill/AutofillService.cs
@@ -9,6 +9,10 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
+#if !FDROID
+using Microsoft.AppCenter.Crashes;
+#endif
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -29,115 +33,138 @@ namespace Bit.Droid.Autofill
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback)
{
- var structure = request.FillContexts?.LastOrDefault()?.Structure;
- if (structure == null)
+ try
{
- return;
- }
-
- var parser = new Parser(structure, ApplicationContext);
- parser.Parse();
-
- if (_storageService == null)
- {
- _storageService = ServiceContainer.Resolve("storageService");
- }
-
- var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
- if (!shouldAutofill)
- {
- return;
- }
-
- var inlineAutofillEnabled = await _storageService.GetAsync(Constants.InlineAutofillEnabledKey) ?? true;
-
- if (_vaultTimeoutService == null)
- {
- _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService");
- }
-
- List items = null;
- await _vaultTimeoutService.CheckVaultTimeoutAsync();
- var locked = await _vaultTimeoutService.IsLockedAsync();
- if (!locked)
- {
- if (_cipherService == null)
+ var structure = request.FillContexts?.LastOrDefault()?.Structure;
+ if (structure == null)
{
- _cipherService = ServiceContainer.Resolve("cipherService");
+ return;
}
- items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService);
- }
- // build response
- var response = AutofillHelpers.BuildFillResponse(parser, items, locked, inlineAutofillEnabled, request);
- callback.OnSuccess(response);
+ var parser = new Parser(structure, ApplicationContext);
+ parser.Parse();
+
+ if (_storageService == null)
+ {
+ _storageService = ServiceContainer.Resolve("storageService");
+ }
+
+ var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
+ if (!shouldAutofill)
+ {
+ return;
+ }
+
+ var inlineAutofillEnabled = await _storageService.GetAsync(Constants.InlineAutofillEnabledKey) ?? true;
+
+ if (_vaultTimeoutService == null)
+ {
+ _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService");
+ }
+
+ List items = null;
+ await _vaultTimeoutService.CheckVaultTimeoutAsync();
+ var locked = await _vaultTimeoutService.IsLockedAsync();
+ if (!locked)
+ {
+ if (_cipherService == null)
+ {
+ _cipherService = ServiceContainer.Resolve("cipherService");
+ }
+ items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService);
+ }
+
+ // build response
+ var response = AutofillHelpers.CreateFillResponse(parser, items, locked, inlineAutofillEnabled, request);
+ var disableSavePrompt = await _storageService.GetAsync(Constants.AutofillDisableSavePromptKey);
+ if (!disableSavePrompt.GetValueOrDefault())
+ {
+ AutofillHelpers.AddSaveInfo(parser, request, response, parser.FieldCollection);
+ }
+ callback.OnSuccess(response.Build());
+ }
+ catch (Exception e)
+ {
+#if !FDROID
+ Crashes.TrackError(e);
+#endif
+ }
}
public async override void OnSaveRequest(SaveRequest request, SaveCallback callback)
{
- var structure = request.FillContexts?.LastOrDefault()?.Structure;
- if (structure == null)
+ try
{
- return;
- }
-
- if (_storageService == null)
- {
- _storageService = ServiceContainer.Resolve("storageService");
- }
-
- var disableSavePrompt = await _storageService.GetAsync(Constants.AutofillDisableSavePromptKey);
- if (disableSavePrompt.GetValueOrDefault())
- {
- return;
- }
-
- _policyService ??= ServiceContainer.Resolve("policyService");
-
- var personalOwnershipPolicyApplies = await _policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
- if (personalOwnershipPolicyApplies)
- {
- return;
- }
-
- var parser = new Parser(structure, ApplicationContext);
- parser.Parse();
-
- var savedItem = parser.FieldCollection.GetSavedItem();
- if (savedItem == null)
- {
- Toast.MakeText(this, "Unable to save this form.", ToastLength.Short).Show();
- return;
- }
-
- var intent = new Intent(this, typeof(MainActivity));
- intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop);
- intent.PutExtra("autofillFramework", true);
- intent.PutExtra("autofillFrameworkSave", true);
- intent.PutExtra("autofillFrameworkType", (int)savedItem.Type);
- switch (savedItem.Type)
- {
- case CipherType.Login:
- intent.PutExtra("autofillFrameworkName", parser.Uri
- .Replace(Constants.AndroidAppProtocol, string.Empty)
- .Replace("https://", string.Empty)
- .Replace("http://", string.Empty));
- intent.PutExtra("autofillFrameworkUri", parser.Uri);
- intent.PutExtra("autofillFrameworkUsername", savedItem.Login.Username);
- intent.PutExtra("autofillFrameworkPassword", savedItem.Login.Password);
- break;
- case CipherType.Card:
- intent.PutExtra("autofillFrameworkCardName", savedItem.Card.Name);
- intent.PutExtra("autofillFrameworkCardNumber", savedItem.Card.Number);
- intent.PutExtra("autofillFrameworkCardExpMonth", savedItem.Card.ExpMonth);
- intent.PutExtra("autofillFrameworkCardExpYear", savedItem.Card.ExpYear);
- intent.PutExtra("autofillFrameworkCardCode", savedItem.Card.Code);
- break;
- default:
- Toast.MakeText(this, "Unable to save this type of form.", ToastLength.Short).Show();
+ var structure = request.FillContexts?.LastOrDefault()?.Structure;
+ if (structure == null)
+ {
return;
+ }
+
+ if (_storageService == null)
+ {
+ _storageService = ServiceContainer.Resolve("storageService");
+ }
+
+ var disableSavePrompt = await _storageService.GetAsync(Constants.AutofillDisableSavePromptKey);
+ if (disableSavePrompt.GetValueOrDefault())
+ {
+ return;
+ }
+
+ _policyService ??= ServiceContainer.Resolve("policyService");
+
+ var personalOwnershipPolicyApplies = await _policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
+ if (personalOwnershipPolicyApplies)
+ {
+ return;
+ }
+
+ var parser = new Parser(structure, ApplicationContext);
+ parser.Parse();
+
+ var savedItem = parser.FieldCollection.GetSavedItem();
+ if (savedItem == null)
+ {
+ Toast.MakeText(this, "Unable to save this form.", ToastLength.Short).Show();
+ return;
+ }
+
+ var intent = new Intent(this, typeof(MainActivity));
+ intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop);
+ intent.PutExtra("autofillFramework", true);
+ intent.PutExtra("autofillFrameworkSave", true);
+ intent.PutExtra("autofillFrameworkType", (int)savedItem.Type);
+ switch (savedItem.Type)
+ {
+ case CipherType.Login:
+ intent.PutExtra("autofillFrameworkName", parser.Uri
+ .Replace(Constants.AndroidAppProtocol, string.Empty)
+ .Replace("https://", string.Empty)
+ .Replace("http://", string.Empty));
+ intent.PutExtra("autofillFrameworkUri", parser.Uri);
+ intent.PutExtra("autofillFrameworkUsername", savedItem.Login.Username);
+ intent.PutExtra("autofillFrameworkPassword", savedItem.Login.Password);
+ break;
+ case CipherType.Card:
+ intent.PutExtra("autofillFrameworkCardName", savedItem.Card.Name);
+ intent.PutExtra("autofillFrameworkCardNumber", savedItem.Card.Number);
+ intent.PutExtra("autofillFrameworkCardExpMonth", savedItem.Card.ExpMonth);
+ intent.PutExtra("autofillFrameworkCardExpYear", savedItem.Card.ExpYear);
+ intent.PutExtra("autofillFrameworkCardCode", savedItem.Card.Code);
+ break;
+ default:
+ Toast.MakeText(this, "Unable to save this type of form.", ToastLength.Short).Show();
+ return;
+ }
+ StartActivity(intent);
+ }
+ catch (Exception e)
+ {
+#if !FDROID
+ Crashes.TrackError(e);
+#endif
}
- StartActivity(intent);
}
}
}
diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index b73e42dc2..c44c83a3a 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -1,25 +1,24 @@
-using Android.App;
-using Android.Content.PM;
-using Android.Runtime;
-using Android.OS;
-using Bit.Core;
-using System.Linq;
-using Bit.App.Abstractions;
-using Bit.Core.Utilities;
-using Bit.Core.Abstractions;
+using System;
using System.IO;
-using System;
-using Android.Content;
-using Bit.Droid.Utilities;
-using Bit.Droid.Receivers;
-using Bit.App.Models;
-using Bit.Core.Enums;
-using Android.Nfc;
+using System.Linq;
using System.Threading.Tasks;
+using Android.App;
+using Android.Content;
+using Android.Content.PM;
+using Android.Nfc;
+using Android.OS;
+using Android.Runtime;
using AndroidX.Core.Content;
+using Bit.App.Abstractions;
+using Bit.App.Models;
using Bit.App.Utilities;
+using Bit.Core;
+using Bit.Core.Abstractions;
+using Bit.Core.Enums;
+using Bit.Core.Utilities;
+using Bit.Droid.Receivers;
+using Bit.Droid.Utilities;
using ZXing.Net.Mobile.Android;
-using Android.Util;
namespace Bit.Droid
{
@@ -120,6 +119,9 @@ namespace Bit.Droid
base.OnResume();
Xamarin.Essentials.Platform.OnResume();
AppearanceAdjustments();
+
+ ThemeManager.UpdateThemeOnPagesAsync();
+
if (_deviceActionService.SupportsNfc())
{
try
diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs
index b2854aebe..1957f70a2 100644
--- a/src/Android/MainApplication.cs
+++ b/src/Android/MainApplication.cs
@@ -18,6 +18,8 @@ using Plugin.Fingerprint;
using Xamarin.Android.Net;
using System.Net.Http;
using System.Net;
+using Bit.App.Utilities;
+using Bit.App.Pages;
#if !FDROID
using Android.Gms.Security;
#endif
@@ -45,6 +47,20 @@ namespace Bit.Droid
var deviceActionService = ServiceContainer.Resolve("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
Constants.AndroidAllClearCipherCacheKeys);
+
+ // TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
+ var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
+ ServiceContainer.Resolve("apiService"),
+ ServiceContainer.Resolve("messagingService"),
+ ServiceContainer.Resolve("platformUtilsService"),
+ ServiceContainer.Resolve("deviceActionService"));
+ ServiceContainer.Register("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
+
+ var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
+ ServiceContainer.Resolve("keyConnectorService"),
+ ServiceContainer.Resolve("passwordRepromptService"),
+ ServiceContainer.Resolve("cryptoService"));
+ ServiceContainer.Register("verificationActionsFlowHelper", verificationActionsFlowHelper);
}
#if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml
index 0f6dd1eff..ac94e44cd 100644
--- a/src/Android/Properties/AndroidManifest.xml
+++ b/src/Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/src/Android/Resources/drawable/card.xml b/src/Android/Resources/drawable/card.xml
index 8a0b07bfc..bf2d50e3d 100644
--- a/src/Android/Resources/drawable/card.xml
+++ b/src/Android/Resources/drawable/card.xml
@@ -1,9 +1,9 @@
+ android:width="640dp"
+ android:height="512dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+ android:pathData="M532.864 80h-425.728c-12.067 0.695-23.371 6.129-31.451 15.118s-12.279 20.808-11.686 32.882v256c-0.594 12.074 3.606 23.891 11.686 32.88 8.079 8.992 19.383 14.425 31.451 15.12h425.599c12.090-0.663 23.427-6.083 31.533-15.075 8.109-8.995 12.327-20.832 11.731-32.925v-256c0.595-12.073-3.606-23.891-11.683-32.882-8.080-8.99-19.385-14.424-31.453-15.118v0zM107.264 112h425.6c4.013 0.248 7.766 2.066 10.451 5.059 2.682 2.995 4.077 6.925 3.885 10.941v28.8c0.186 3.465-1.011 6.862-3.328 9.447-2.313 2.585-5.558 4.147-9.024 4.345h-429.728c-3.459-0.207-6.695-1.773-9.003-4.356s-3.501-5.976-3.317-9.436v-28.8c-0.192-4.016 1.204-7.946 3.886-10.941s6.437-4.812 10.45-5.059h0.128zM532.864 399.808h-425.728c-3.978-0.247-7.703-2.038-10.38-4.992-2.677-2.95-4.097-6.832-3.956-10.816v-167.424c-0.183-3.465 1.013-6.862 3.329-9.446s5.559-4.147 9.023-4.345h429.76c3.466 0.198 6.711 1.761 9.024 4.345 2.317 2.584 3.514 5.982 3.328 9.447v167.424c0.192 4.026-1.213 7.965-3.907 10.963-2.697 2.995-6.467 4.806-10.493 5.037v-0.192zM494.496 340.96h-75.296c-2.717 0.358-5.213 1.69-7.021 3.75s-2.803 4.711-2.803 7.45c0 2.743 0.995 5.389 2.803 7.45s4.304 3.395 7.021 3.75h75.424c2.72-0.355 5.216-1.69 7.024-3.75s2.803-4.707 2.803-7.45c0-2.739-0.995-5.389-2.803-7.45s-4.304-3.392-7.024-3.75h-0.128z" />
diff --git a/src/Android/Resources/drawable/cog.xml b/src/Android/Resources/drawable/cog.xml
index a7a5a37ce..311d0ad0c 100644
--- a/src/Android/Resources/drawable/cog.xml
+++ b/src/Android/Resources/drawable/cog.xml
@@ -1,9 +1,9 @@
+ android:width="640dp"
+ android:height="512dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+ android:pathData="M532.989 289.887l-3.872-2.528c-3.197-1.866-5.744-4.667-7.299-8.026-1.558-3.358-2.048-7.113-1.405-10.759v-24.64c-0.682-3.57-0.202-7.266 1.37-10.542s4.154-5.964 7.366-7.666l4.768-2.4c12.013-7.054 20.768-18.547 24.384-32 3.421-13.333 1.661-27.466-4.928-39.552l-25.056-43.872c-7.011-11.727-18.259-20.313-31.418-23.987-13.161-3.674-27.229-2.155-39.303 4.243l-4.384 2.208c-3.286 1.769-6.983 2.63-10.711 2.496-3.731-0.135-7.357-1.261-10.505-3.263-7.082-4.796-14.579-8.951-22.4-12.416-3.28-1.636-6.038-4.157-7.962-7.278s-2.935-6.719-2.918-10.386v-6.528c0.099-6.965-1.197-13.879-3.808-20.335s-6.49-12.326-11.401-17.264c-4.915-4.937-10.765-8.842-17.209-11.486s-13.351-3.972-20.317-3.907h-51.2c-6.952-0.043-13.842 1.301-20.267 3.954s-12.257 6.561-17.154 11.496c-4.896 4.935-8.758 10.797-11.361 17.243s-3.892 13.347-3.794 20.298v5.472c0.032 3.614-0.938 7.165-2.802 10.261s-4.55 5.614-7.758 7.275c-5.691 2.572-11.197 5.533-16.48 8.864l-6.080 3.584c-3.102 2.221-6.788 3.481-10.6 3.623s-7.582-0.839-10.84-2.823l-3.968-1.952c-5.856-3.516-12.377-5.778-19.153-6.642s-13.656-0.314-20.208 1.618c-13.446 3.716-24.885 12.58-31.84 24.672l-24.96 43.68c-3.566 6.048-5.867 12.757-6.763 19.721s-0.37 14.037 1.547 20.791c1.743 6.495 4.779 12.571 8.925 17.866s9.317 9.699 15.203 12.95l2.88 2.848 1.312 0.928c3.197 1.867 5.744 4.667 7.3 8.026s2.046 7.113 1.403 10.758v24.704c0.326 3.533-0.314 7.087-1.853 10.283s-3.918 5.913-6.883 7.861l-4.768 2.4c-11.724 7.217-20.258 18.63-23.866 31.917s-2.020 27.447 4.442 39.603l25.088 43.872c6.806 11.955 18.058 20.739 31.308 24.445 13.25 3.702 27.425 2.026 39.445-4.669l4.352-2.176c3.287-1.792 6.994-2.669 10.736-2.547 3.742 0.125 7.382 1.248 10.544 3.251 7.082 4.797 14.578 8.954 22.4 12.416 3.281 1.635 6.038 4.157 7.962 7.28 1.923 3.12 2.934 6.717 2.918 10.384v5.472c-0.102 6.954 1.185 13.859 3.788 20.31s6.468 12.317 11.368 17.251c4.901 4.938 10.738 8.845 17.169 11.495s13.327 3.987 20.282 3.936h51.2c6.957 0.051 13.856-1.286 20.288-3.936s12.272-6.557 17.175-11.491c4.902-4.938 8.771-10.8 11.379-17.251 2.605-6.451 3.897-13.357 3.798-20.313v-5.472c-0.032-3.613 0.938-7.165 2.803-10.259 1.863-3.098 4.547-5.616 7.757-7.277 5.683-2.567 11.181-5.526 16.448-8.864l1.376-0.8 4.704-2.784c3.111-2.211 6.803-3.466 10.618-3.606 3.815-0.144 7.587 0.832 10.854 2.807l3.968 1.952c5.993 3.568 12.653 5.878 19.565 6.791 6.915 0.912 13.945 0.409 20.659-1.478 6.599-1.805 12.755-4.95 18.080-9.248 5.325-4.295 9.706-9.645 12.864-15.712l24.96-43.68c3.504-5.907 5.757-12.474 6.615-19.289 0.861-6.816 0.307-13.735-1.622-20.327-3.584-13.397-12.298-24.846-24.256-31.873zM319.997 346.752c-17.949 0-35.495-5.322-50.419-15.296-14.924-9.971-26.556-24.144-33.424-40.727s-8.666-34.83-5.165-52.434c3.502-17.604 12.145-33.775 24.837-46.466s28.862-21.335 46.466-24.837c17.604-3.502 35.852-1.704 52.434 5.164s30.755 18.501 40.73 33.425c9.971 14.924 15.293 32.47 15.293 50.419 0 24.069-9.562 47.153-26.579 64.17-17.021 17.021-40.103 26.582-64.173 26.582z" />
diff --git a/src/Android/Resources/drawable/generate.xml b/src/Android/Resources/drawable/generate.xml
new file mode 100644
index 000000000..4ddeaef23
--- /dev/null
+++ b/src/Android/Resources/drawable/generate.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/Android/Resources/drawable/id.xml b/src/Android/Resources/drawable/id.xml
index 8ad10d3d2..cf1e49c9b 100644
--- a/src/Android/Resources/drawable/id.xml
+++ b/src/Android/Resources/drawable/id.xml
@@ -1,9 +1,9 @@
+ android:width="640dp"
+ android:height="512dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+ android:pathData="M261.411 257.983c8.966-7.451 15.421-17.482 18.488-28.73s2.595-23.167-1.349-34.138c-3.945-10.97-11.171-20.461-20.697-27.181s-20.891-10.345-32.548-10.381c-11.658-0.036-23.044 3.518-32.612 10.18s-16.852 16.107-20.863 27.053c-4.012 10.946-4.556 22.862-1.559 34.129s9.39 21.337 18.31 28.843c-13.738 5.811-25.873 14.849-35.375 26.346s-16.095 25.116-19.216 39.704c-0.78 3.654-0.728 7.44 0.153 11.075 0.881 3.632 2.567 7.021 4.935 9.917 2.31 2.864 5.236 5.175 8.559 6.759s6.96 2.403 10.642 2.394h132.928c3.682 0.010 7.318-0.81 10.642-2.394s6.248-3.894 8.558-6.759c2.366-2.896 4.053-6.285 4.939-9.917s0.948-7.418 0.181-11.075c-3.106-14.498-9.639-28.038-19.054-39.491s-21.436-20.483-35.058-26.334v0zM225.123 180.319c6.798 0 13.442 2.016 19.094 5.792s10.056 9.144 12.658 15.423c2.601 6.28 3.282 13.19 1.956 19.857s-4.6 12.791-9.406 17.597c-4.806 4.806-10.93 8.079-17.597 9.406s-13.577 0.645-19.857-1.956c-6.28-2.601-11.648-7.006-15.423-12.658s-5.792-12.297-5.792-19.094c0.008-9.113 3.632-17.85 10.076-24.292s15.18-10.067 24.292-10.076v0zM291.202 331.552h-132.928l-2.368-3.2c3.409-15.714 12.094-29.788 24.61-39.88s28.112-15.596 44.19-15.596c16.079 0 31.673 5.504 44.19 15.596s21.202 24.166 24.61 39.88l-2.304 3.2zM369.376 202.178h80.288c2.825-0.214 5.466-1.487 7.389-3.563 1.926-2.077 2.998-4.804 2.998-7.637s-1.072-5.56-2.998-7.637c-1.923-2.077-4.563-3.35-7.389-3.563h-80.288c-2.822 0.214-5.462 1.487-7.389 3.563s-2.995 4.804-2.995 7.637c0 2.833 1.069 5.56 2.995 7.637s4.567 3.35 7.389 3.563v0zM484.445 309.699h-115.2c-2.825 0.214-5.466 1.486-7.389 3.563-1.926 2.077-2.995 4.805-2.995 7.637s1.069 5.558 2.995 7.639c1.923 2.077 4.563 3.347 7.389 3.562h115.2c2.822-0.214 5.462-1.485 7.389-3.562 1.926-2.080 2.995-4.807 2.995-7.639s-1.069-5.56-2.995-7.637c-1.926-2.077-4.567-3.349-7.389-3.563v0zM484.448 244.738h-115.2c-2.973 0-5.821 1.18-7.92 3.28-2.103 2.101-3.28 4.95-3.28 7.919s1.178 5.819 3.28 7.92c2.099 2.101 4.947 3.281 7.92 3.281h115.2c2.969 0 5.817-1.18 7.92-3.281 2.099-2.101 3.28-4.949 3.28-7.919s-1.181-5.819-3.28-7.919c-2.103-2.1-4.95-3.28-7.92-3.28v0zM544 80h-448c-8.487 0-16.626 3.371-22.627 9.373s-9.373 14.141-9.373 22.627v288c0 8.486 3.371 16.627 9.373 22.627s14.141 9.373 22.627 9.373h448c8.486 0 16.627-3.373 22.627-9.373s9.373-14.141 9.373-22.627v-288c0-8.487-3.373-16.626-9.373-22.627s-14.141-9.373-22.627-9.373v0zM528 400h-416c-4.243 0-8.314-1.687-11.314-4.685-3.001-3.002-4.686-7.072-4.686-11.315v-256c0-4.243 1.686-8.314 4.686-11.314s7.070-4.686 11.314-4.686h418.4c2.816 0.64 13.376 3.712 13.6 13.824v258.176c0 4.243-1.687 8.313-4.685 11.315-3.001 2.998-7.072 4.685-11.315 4.685z" />
diff --git a/src/Android/Resources/drawable/info.xml b/src/Android/Resources/drawable/info.xml
index 715b90c9b..19d995f06 100644
--- a/src/Android/Resources/drawable/info.xml
+++ b/src/Android/Resources/drawable/info.xml
@@ -1,9 +1,9 @@
-
+ android:width="640dp"
+ android:height="512dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+
diff --git a/src/Android/Resources/drawable/lock.xml b/src/Android/Resources/drawable/lock.xml
index 492570d76..b65e3f89e 100644
--- a/src/Android/Resources/drawable/lock.xml
+++ b/src/Android/Resources/drawable/lock.xml
@@ -1,9 +1,9 @@
+ android:width="640dp"
+ android:height="512dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+ android:pathData="M484.32 163.488h-14.528c-1.142-0.004-2.275-0.233-3.328-0.674-1.056-0.441-2.013-1.086-2.816-1.898-0.807-0.811-1.443-1.773-1.878-2.831-0.432-1.058-0.653-2.191-0.649-3.333v-6.4c0.675-35.916-11.907-70.822-35.341-98.047-23.437-27.225-56.083-44.861-91.699-49.536-19.685-1.895-39.55 0.355-58.314 6.603s-36.011 16.357-50.632 29.675c-14.62 13.318-26.29 29.55-34.255 47.651s-12.054 37.671-11.999 57.447v10.24c0 0.224-0.768 10.944-10.080 11.104h-13.088c-5.76 0.017-11.46 1.168-16.774 3.389s-10.139 5.467-14.198 9.553c-4.059 4.086-7.274 8.932-9.46 14.262s-3.3 11.036-3.278 16.796v260.576c0.025 11.606 4.632 22.733 12.817 30.96s19.29 12.893 30.895 12.976h328.608c11.6-0.077 22.701-4.733 30.883-12.957 8.179-8.224 12.781-19.347 12.797-30.947v-260.608c0.023-5.757-1.091-11.462-3.277-16.789-2.183-5.327-5.395-10.172-9.449-14.257-4.058-4.086-8.877-7.333-14.189-9.555s-11.008-3.377-16.765-3.399v0zM331.808 391.104v43.072c0.083 1.6-0.16 3.2-0.714 4.704-0.557 1.504-1.411 2.877-2.515 4.041-1.101 1.161-2.429 2.087-3.904 2.72-1.472 0.63-3.056 0.957-4.659 0.957-1.602 0-3.188-0.327-4.66-0.957-1.473-0.634-2.8-1.558-3.904-2.72-1.103-1.165-1.958-2.538-2.513-4.041s-0.798-3.104-0.715-4.704v-43.072c-5.285-2.653-9.519-7.014-12.019-12.375s-3.117-11.408-1.753-17.161c1.364-5.753 4.63-10.88 9.269-14.55 4.639-3.667 10.38-5.661 16.295-5.661s11.654 1.993 16.294 5.661c4.64 3.671 7.904 8.797 9.271 14.55 1.363 5.753 0.745 11.801-1.753 17.161s-6.733 9.721-12.019 12.375v0zM429.12 154.752c0.003 1.146-0.217 2.281-0.653 3.341s-1.075 2.023-1.885 2.834c-0.81 0.812-1.769 1.456-2.829 1.895-1.056 0.44-2.192 0.666-3.337 0.666h-196.16c-1.624 0.141-3.258-0.042-4.81-0.536s-2.992-1.291-4.235-2.344c-1.244-1.053-2.266-2.341-3.010-3.792s-1.192-3.032-1.321-4.656v-10.016c-0.043-16.317 3.56-32.439 10.547-47.185s17.179-27.745 29.833-38.047c12.654-10.302 27.451-17.647 43.308-21.497s32.375-4.11 48.344-0.758c24.841 5.834 46.941 19.992 62.624 40.121s24.003 45.020 23.584 70.534v9.44z" />
diff --git a/src/Android/Resources/drawable/login.xml b/src/Android/Resources/drawable/login.xml
index 1c0811a19..01fa78a31 100644
--- a/src/Android/Resources/drawable/login.xml
+++ b/src/Android/Resources/drawable/login.xml
@@ -1,9 +1,9 @@
+ android:width="640dp"
+ android:height="512dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+ android:pathData="M571.2 205.825c-9.082-45.471-30.339-87.624-61.501-121.963-31.159-34.339-71.056-59.575-115.437-73.018-44.377-13.443-91.573-14.586-136.551-3.311s-86.052 34.55-118.84 67.338c-32.788 32.788-56.062 73.862-67.338 118.84s-10.132 92.173 3.311 136.551c13.442 44.381 38.679 84.279 73.018 115.437 34.339 31.161 76.492 52.419 121.964 61.501 41.367 8.409 84.172 6.442 124.594-5.728 40.419-12.166 77.197-34.157 107.046-64.007s51.84-66.627 64.007-107.046c12.169-40.422 14.138-83.228 5.728-124.594zM186.305 76.481c38.576-28.909 85.49-44.517 133.695-44.48 2.752 0 5.376 0.48 8.128 0.608 9.952 2.88 2.368 11.104 2.368 11.104-34.061 17.672-62.726 44.203-82.975 76.8-9.6 15.488-17.216 15.328-19.68 15.040-14.112-0.608-31.456-20.224-44.608-40.16-1.954-2.963-2.723-6.552-2.154-10.055s2.434-6.665 5.226-8.856v0zM253.505 461.184c-1.923 1.971-4.388 3.328-7.083 3.897s-5.497 0.327-8.054-0.697c-40.582-15.911-75.645-43.305-100.902-78.832s-39.611-77.643-41.306-121.199l9.44-13.888c30.432-11.36 60.416-17.504 68.512-10.496 3.744 3.2 1.184 13.12 0 17.216-22.88 74.143 36.288 93.183 58.624 100.383 2.464 0.8 4.576 1.504 6.4 2.112 23.392 9.024 36.736 22.112 39.712 38.848 0.56 11.645-1.404 23.274-5.757 34.087s-10.99 20.56-19.459 28.57h-0.128zM477.152 414.656c-41.667 41.728-98.183 65.226-157.152 65.344-9.798-0.067-19.581-0.807-29.279-2.208-1.884-0.298-3.663-1.063-5.178-2.221-1.514-1.161-2.716-2.678-3.498-4.419s-1.117-3.648-0.976-5.549c0.14-1.901 0.752-3.741 1.779-5.347v0c14.127-18.771 20.486-42.262 17.76-65.6-3.027-13.315-9.63-25.552-19.099-35.392s-21.44-16.909-34.63-20.448c-1.984-0.768-4.544-1.632-7.488-2.56-30.592-9.856-59.168-23.551-44.192-72.447 7.040-22.848 0.256-34.784-6.656-40.768-14.72-12.8-42.976-8.928-68.96-1.216-2.377 0.642-4.879 0.653-7.262 0.034s-4.563-1.849-6.326-3.566c-1.763-1.718-3.049-3.865-3.73-6.231s-0.734-4.868-0.155-7.26c7.821-33.432 23.251-64.608 45.088-91.104 1.379-1.599 3.102-2.864 5.042-3.698s4.043-1.216 6.151-1.118c2.109 0.099 4.167 0.675 6.020 1.688s3.452 2.431 4.674 4.152c14.304 20.224 35.040 42.4 57.6 43.392h1.6c8.108-0.433 15.946-3.062 22.676-7.605s12.098-10.829 15.532-18.188c25.389-38.702 62.678-68.096 106.239-83.744v0c9.504-2.946 19.648-3.124 29.248-0.512 29.693 11.669 56.55 29.538 78.784 52.416 1.798 1.852 3.053 4.159 3.635 6.674s0.467 5.138-0.333 7.591c-0.8 2.453-2.256 4.641-4.208 6.329s-4.327 2.81-6.87 3.247c-41.088 7.136-94.208 21.184-101.088 46.816-3.2 12.416 2.912 24.48 18.56 35.872 60.288 43.744 77.984 77.344 68.768 91.232-8.49 10.898-14.24 23.673-16.774 37.254-2.534 13.577-1.773 27.568 2.214 40.793 5.907 10.624 15.488 18.726 26.944 22.784 0 0 11.36 6.528 5.664 15.584h-0.128zM512 369.664c-2.966 3.639-6.743 6.531-11.024 8.448-4.285 1.917-8.96 2.807-13.648 2.592-4.595-0.729-8.992-2.409-12.909-4.925-3.917-2.519-7.267-5.821-9.843-9.699-7.584-14.624 2.432-38.944 13.312-55.359 11.296-17.12 16.736-55.68-74.304-121.76-7.648-5.536-10.592-10.080-10.080-11.936 3.2-11.808 52.608-27.072 108.8-34.336 2.845-0.366 5.735 0.123 8.298 1.405 2.567 1.282 4.691 3.299 6.102 5.794 18.557 33.828 27.891 71.942 27.066 110.518s-11.783 76.255-31.769 109.257v0z" />
diff --git a/src/Android/Resources/drawable/paper_plane.xml b/src/Android/Resources/drawable/paper_plane.xml
deleted file mode 100644
index 1ad7fd94c..000000000
--- a/src/Android/Resources/drawable/paper_plane.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/src/Android/Resources/drawable/pencil.xml b/src/Android/Resources/drawable/pencil.xml
index 6c86b9cb8..34a33155f 100644
--- a/src/Android/Resources/drawable/pencil.xml
+++ b/src/Android/Resources/drawable/pencil.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="16dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+ android:pathData="M558.781 17.216c-6.342-6.397-14.083-11.239-22.608-14.148-8.528-2.909-17.613-3.805-26.544-2.619-20.425 3.183-39.178 13.174-53.216 28.352l-323.807 323.872c-11.904 11.872-21.977 25.453-29.888 40.288l-33.696 64.64c-4.079 6.957-5.793 15.046-4.887 23.059s4.382 15.517 9.911 21.389c3.263 3.216 7.133 5.753 11.386 7.462s8.8 2.554 13.382 2.489c6.858-0.067 13.599-1.792 19.648-5.024l64.512-33.632c14.925-7.888 28.573-17.984 40.48-29.952l323.744-323.808c15.187-14.022 25.191-32.763 28.384-53.184 1.159-8.938 0.246-18.022-2.669-26.549-2.912-8.529-7.747-16.273-14.131-22.634v0zM483.741 137.6l-283.103 282.976c-9.575 9.632-20.546 17.767-32.544 24.128l-64.576 33.664c-3.2 1.76-5.76 1.6-6.656 0.736s-0.992-3.2 0.768-6.688l33.728-64.512c6.355-11.949 14.466-22.873 24.064-32.416l282.975-282.688c0.599-0.624 1.315-1.121 2.112-1.461 0.793-0.34 1.648-0.514 2.512-0.514s1.721 0.175 2.515 0.514c0.793 0.34 1.514 0.837 2.109 1.461l36.032 35.2c0.659 0.612 1.187 1.352 1.549 2.175s0.55 1.712 0.557 2.611c0.007 0.899-0.17 1.79-0.522 2.618s-0.867 1.575-1.52 2.196v0zM524.381 96.928l-16.352 16.384c-1.034 0.882-2.352 1.365-3.712 1.365s-2.675-0.484-3.712-1.365l-38.4-37.568c-0.803-0.851-1.286-1.957-1.36-3.125-0.077-1.168 0.256-2.326 0.944-3.275l17.376-17.376c8.906-10.032 20.915-16.791 34.112-19.2 1.222-0.153 2.451-0.227 3.68-0.224 3.517-0.046 7.005 0.616 10.259 1.947s6.208 3.302 8.685 5.797c13.76 13.472 9.216 35.744-11.52 56.48v0.16z" />
diff --git a/src/Android/Resources/drawable/plus.xml b/src/Android/Resources/drawable/plus.xml
index 3ee9e23a9..af0a104cd 100644
--- a/src/Android/Resources/drawable/plus.xml
+++ b/src/Android/Resources/drawable/plus.xml
@@ -1,9 +1,9 @@
-
+ android:width="20dp"
+ android:height="16dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+
diff --git a/src/Android/Resources/drawable/refresh.xml b/src/Android/Resources/drawable/refresh.xml
deleted file mode 100644
index 9a8945502..000000000
--- a/src/Android/Resources/drawable/refresh.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/src/Android/Resources/drawable/search.xml b/src/Android/Resources/drawable/search.xml
index 6a5ca808f..300022a56 100644
--- a/src/Android/Resources/drawable/search.xml
+++ b/src/Android/Resources/drawable/search.xml
@@ -1,9 +1,9 @@
-
+ android:width="25dp"
+ android:height="20dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+
diff --git a/src/Android/Resources/drawable/send.xml b/src/Android/Resources/drawable/send.xml
new file mode 100644
index 000000000..826daffe5
--- /dev/null
+++ b/src/Android/Resources/drawable/send.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/Android/Resources/drawable/shield.xml b/src/Android/Resources/drawable/shield.xml
index e07e63eb8..e9788c691 100644
--- a/src/Android/Resources/drawable/shield.xml
+++ b/src/Android/Resources/drawable/shield.xml
@@ -1,9 +1,9 @@
+ android:width="640dp"
+ android:height="512dp"
+ android:viewportWidth="640"
+ android:viewportHeight="512">
+ android:pathData="M526.976 6.401c-1.929-2.029-4.253-3.644-6.826-4.745-2.576-1.101-5.351-1.663-8.151-1.655h-383.998c-2.802-0.016-5.576 0.543-8.153 1.645s-4.898 2.719-6.823 4.755c-2.031 1.928-3.647 4.252-4.748 6.827s-1.663 5.348-1.653 8.149v256c0.075 19.489 3.855 38.786 11.136 56.863 6.93 17.843 16.24 34.669 27.68 50.016 11.758 15.37 24.924 29.606 39.328 42.528 13.368 12.256 27.44 23.721 42.144 34.336 12.8 9.088 26.24 17.696 40.32 25.824s24.021 13.623 29.824 16.48c5.856 2.88 10.592 5.152 14.112 6.656 2.752 1.325 5.778 1.984 8.83 1.92 3.011 0.042 5.987-0.653 8.672-2.016 3.584-1.568 8.256-3.776 14.176-6.656s16-8.384 29.824-16.48c13.824-8.096 27.424-16.736 40.32-25.824 14.723-10.618 28.816-22.083 42.208-34.336 14.419-12.906 27.587-27.146 39.328-42.528 11.43-15.353 20.739-32.176 27.68-50.016 7.293-18.074 11.072-37.373 11.136-56.863v-256c0.013-2.784-0.544-5.541-1.641-8.101-1.095-2.559-2.704-4.867-4.726-6.779v0zM477.472 279.712c0 92.799-157.472 172.512-157.472 172.512v-397.375h157.472v224.864z" />
diff --git a/src/Android/Resources/layout/progress_dialog_layout.xml b/src/Android/Resources/layout/progress_dialog_layout.xml
new file mode 100644
index 000000000..0515dc3e1
--- /dev/null
+++ b/src/Android/Resources/layout/progress_dialog_layout.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Android/Resources/xml/autofillservice.xml b/src/Android/Resources/xml/autofillservice.xml
index d44dbfbd9..20432c86b 100644
--- a/src/Android/Resources/xml/autofillservice.xml
+++ b/src/Android/Resources/xml/autofillservice.xml
@@ -77,6 +77,9 @@
+
diff --git a/src/Android/Services/AndroidPushNotificationService.cs b/src/Android/Services/AndroidPushNotificationService.cs
index 975cbd2db..097c5f708 100644
--- a/src/Android/Services/AndroidPushNotificationService.cs
+++ b/src/Android/Services/AndroidPushNotificationService.cs
@@ -1,6 +1,7 @@
#if !FDROID
using System;
using System.Threading.Tasks;
+using AndroidX.Core.App;
using Bit.App.Abstractions;
using Bit.Core;
using Bit.Core.Abstractions;
@@ -21,6 +22,8 @@ namespace Bit.Droid.Services
_pushNotificationListenerService = pushNotificationListenerService;
}
+ public bool IsRegisteredForPush => NotificationManagerCompat.From(Android.App.Application.Context)?.AreNotificationsEnabled() ?? false;
+
public async Task GetTokenAsync()
{
return await _storageService.GetAsync(Constants.PushCurrentTokenKey);
diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs
index 9bb004161..c83689c97 100644
--- a/src/Android/Services/DeviceActionService.cs
+++ b/src/Android/Services/DeviceActionService.cs
@@ -13,6 +13,7 @@ using Android.OS;
using Android.Provider;
using Android.Text;
using Android.Text.Method;
+using Android.Views;
using Android.Views.Autofill;
using Android.Views.InputMethods;
using Android.Webkit;
@@ -27,6 +28,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Droid.Autofill;
+using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
namespace Bit.Droid.Services
@@ -37,7 +39,9 @@ namespace Bit.Droid.Services
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly Func _eventServiceFunc;
- private ProgressDialog _progressDialog;
+ private AlertDialog _progressDialog;
+ object _progressDialogLock = new object();
+
private bool _cameraPermissionsDenied;
private Toast _toast;
private string _userAgent;
@@ -108,22 +112,101 @@ namespace Bit.Droid.Services
{
await HideLoadingAsync();
}
- var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
- _progressDialog = new ProgressDialog(activity);
- _progressDialog.SetMessage(text);
- _progressDialog.SetCancelable(false);
+
+ var activity = CrossCurrentActivity.Current.Activity;
+ var inflater = (LayoutInflater)activity.GetSystemService(Context.LayoutInflaterService);
+ var dialogView = inflater.Inflate(Resource.Layout.progress_dialog_layout, null);
+
+ var txtLoading = dialogView.FindViewById(Resource.Id.txtLoading);
+ txtLoading.Text = text;
+ txtLoading.SetTextColor(ThemeHelpers.TextColor);
+
+ _progressDialog = new AlertDialog.Builder(activity)
+ .SetView(dialogView)
+ .SetCancelable(false)
+ .Create();
_progressDialog.Show();
}
public Task HideLoadingAsync()
{
- if (_progressDialog != null)
+ // Based on https://github.com/redth-org/AndHUD/blob/master/AndHUD/AndHUD.cs
+ lock (_progressDialogLock)
{
- _progressDialog.Dismiss();
- _progressDialog.Dispose();
- _progressDialog = null;
+ if (_progressDialog is null)
+ {
+ return Task.CompletedTask;
+ }
+
+ void actionDismiss()
+ {
+ try
+ {
+ if (IsAlive(_progressDialog) && IsAlive(_progressDialog.Window))
+ {
+ _progressDialog.Hide();
+ _progressDialog.Dismiss();
+ }
+ }
+ catch
+ {
+ // ignore
+ }
+
+ _progressDialog = null;
+ }
+
+ // First try the SynchronizationContext
+ if (Application.SynchronizationContext != null)
+ {
+ Application.SynchronizationContext.Send(state => actionDismiss(), null);
+ return Task.CompletedTask;
+ }
+
+ // Otherwise try OwnerActivity on dialog
+ var ownerActivity = _progressDialog?.OwnerActivity;
+ if (IsAlive(ownerActivity))
+ {
+ ownerActivity.RunOnUiThread(actionDismiss);
+ return Task.CompletedTask;
+ }
+
+ // Otherwise try get it from the Window Context
+ if (_progressDialog?.Window?.Context is Activity windowActivity && IsAlive(windowActivity))
+ {
+ windowActivity.RunOnUiThread(actionDismiss);
+ return Task.CompletedTask;
+ }
+
+ // Finally if all else fails, let's see if current activity is MainActivity
+ if (CrossCurrentActivity.Current.Activity is MainActivity activity && IsAlive(activity))
+ {
+ activity.RunOnUiThread(actionDismiss);
+ return Task.CompletedTask;
+ }
+
+ return Task.CompletedTask;
}
- return Task.FromResult(0);
+ }
+
+ bool IsAlive(Java.Lang.Object @object)
+ {
+ if (@object == null)
+ return false;
+
+ if (@object.Handle == IntPtr.Zero)
+ return false;
+
+ if (@object is Activity activity)
+ {
+ if (activity.IsFinishing)
+ return false;
+
+ if (activity.IsDestroyed)
+ return false;
+ }
+
+ return true;
}
public bool OpenFile(byte[] fileData, string id, string fileName)
diff --git a/src/Android/Tiles/GeneratorTileService.cs b/src/Android/Tiles/GeneratorTileService.cs
index 5a0058270..744b4add8 100644
--- a/src/Android/Tiles/GeneratorTileService.cs
+++ b/src/Android/Tiles/GeneratorTileService.cs
@@ -15,7 +15,7 @@ using Java.Lang;
namespace Bit.Droid.Tile
{
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/PasswordGenerator",
- Icon = "@drawable/refresh")]
+ Icon = "@drawable/generate")]
[IntentFilter(new string[] { ActionQsTile })]
[Register("com.x8bit.bitwarden.GeneratorTileService")]
public class GeneratorTileService : TileService
diff --git a/src/Android/Utilities/ThemeHelpers.cs b/src/Android/Utilities/ThemeHelpers.cs
index 017985b71..b33aabc53 100644
--- a/src/Android/Utilities/ThemeHelpers.cs
+++ b/src/Android/Utilities/ThemeHelpers.cs
@@ -36,6 +36,10 @@ namespace Bit.Droid.Utilities
{
get => ThemeManager.GetResourceColor("SwitchThumbColor").ToAndroid();
}
+ public static Color TextColor
+ {
+ get => ThemeManager.GetResourceColor("TextColor").ToAndroid();
+ }
public static void SetAppearance(string theme, bool osDarkModeEnabled)
{
diff --git a/src/App/Abstractions/IPushNotificationService.cs b/src/App/Abstractions/IPushNotificationService.cs
index ae227f292..c4e3827cb 100644
--- a/src/App/Abstractions/IPushNotificationService.cs
+++ b/src/App/Abstractions/IPushNotificationService.cs
@@ -4,6 +4,7 @@ namespace Bit.App.Abstractions
{
public interface IPushNotificationService
{
+ bool IsRegisteredForPush { get; }
Task GetTokenAsync();
Task RegisterAsync();
Task UnregisterAsync();
diff --git a/src/App/App.csproj b/src/App/App.csproj
index 98b0bb886..3f33e9ebb 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -20,6 +20,7 @@
+
@@ -415,5 +416,6 @@
+
diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs
index ab31a0818..44c6531f8 100644
--- a/src/App/App.xaml.cs
+++ b/src/App/App.xaml.cs
@@ -8,6 +8,7 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
+using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@@ -96,7 +97,7 @@ namespace Bit.App
{
if (Device.RuntimePlatform == Device.iOS)
{
- ResumedAsync();
+ ResumedAsync().FireAndForget();
}
}
else if (message.Command == "slept")
@@ -176,6 +177,8 @@ namespace Bit.App
if (Device.RuntimePlatform == Device.Android)
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
+ // Reset delay on every start
+ _vaultTimeoutService.DelayLockAndLogoutMs = null;
}
_messagingService.Send("startEventTimer");
}
@@ -202,21 +205,21 @@ namespace Bit.App
_isResumed = true;
if (Device.RuntimePlatform == Device.Android)
{
- ResumedAsync();
+ ResumedAsync().FireAndForget();
}
}
private async Task SleptAsync()
{
- await HandleVaultTimeoutAsync();
+ await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("stopEventTimer");
}
- private async void ResumedAsync()
+ private async Task ResumedAsync()
{
- UpdateTheme();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer");
+ await UpdateThemeAsync();
await ClearCacheIfNeededAsync();
Prime();
SyncIfNeeded();
@@ -226,6 +229,15 @@ namespace Bit.App
}
}
+ public async Task UpdateThemeAsync()
+ {
+ await Device.InvokeOnMainThreadAsync(() =>
+ {
+ ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
+ _messagingService.Send("updatedTheme");
+ });
+ }
+
private void SetCulture()
{
// Calendars are removed by linker. ref https://bugzilla.xamarin.com/show_bug.cgi?id=59077
@@ -279,33 +291,6 @@ namespace Bit.App
}
}
- private async Task HandleVaultTimeoutAsync()
- {
- if (await _vaultTimeoutService.IsLockedAsync())
- {
- return;
- }
- var authed = await _userService.IsAuthenticatedAsync();
- if (!authed)
- {
- return;
- }
- var vaultTimeout = await _storageService.GetAsync(Constants.VaultTimeoutKey);
- vaultTimeout = vaultTimeout.GetValueOrDefault(-1);
- if (vaultTimeout == 0)
- {
- var action = await _storageService.GetAsync(Constants.VaultTimeoutActionKey);
- if (action == "logOut")
- {
- await _vaultTimeoutService.LogOutAsync();
- }
- else
- {
- await _vaultTimeoutService.LockAsync(true);
- }
- }
- }
-
private async Task ClearCacheIfNeededAsync()
{
var lastClear = await _storageService.GetAsync(Constants.LastFileCacheClearKey);
@@ -354,7 +339,7 @@ namespace Bit.App
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
Current.RequestedThemeChanged += (s, a) =>
{
- UpdateTheme();
+ UpdateThemeAsync();
};
Current.MainPage = new HomePage();
var mainPageTask = SetMainPageAsync();
@@ -378,15 +363,6 @@ namespace Bit.App
});
}
- private void UpdateTheme()
- {
- Device.BeginInvokeOnMainThread(() =>
- {
- ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
- _messagingService.Send("updatedTheme");
- });
- }
-
private async Task LockedAsync(bool autoPromptBiometric)
{
await _stateService.PurgeAsync();
diff --git a/src/App/Controls/CipherViewCell/CipherViewCell.xaml b/src/App/Controls/CipherViewCell/CipherViewCell.xaml
index 433a8cef6..9f2d6d2f4 100644
--- a/src/App/Controls/CipherViewCell/CipherViewCell.xaml
+++ b/src/App/Controls/CipherViewCell/CipherViewCell.xaml
@@ -5,6 +5,7 @@
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
+ xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
@@ -27,7 +28,7 @@
-
-
-
diff --git a/src/App/Controls/FaButton.cs b/src/App/Controls/IconButton.cs
similarity index 64%
rename from src/App/Controls/FaButton.cs
rename to src/App/Controls/IconButton.cs
index f6528fd9c..e7f8cd787 100644
--- a/src/App/Controls/FaButton.cs
+++ b/src/App/Controls/IconButton.cs
@@ -2,18 +2,18 @@
namespace Bit.App.Controls
{
- public class FaButton : Button
+ public class IconButton : Button
{
- public FaButton()
+ public IconButton()
{
Padding = 0;
switch (Device.RuntimePlatform)
{
case Device.iOS:
- FontFamily = "FontAwesome";
+ FontFamily = "bwi-font";
break;
case Device.Android:
- FontFamily = "FontAwesome.ttf#FontAwesome";
+ FontFamily = "bwi-font.ttf#bwi-font";
break;
}
}
diff --git a/src/App/Controls/FaLabel.cs b/src/App/Controls/IconLabel.cs
similarity index 62%
rename from src/App/Controls/FaLabel.cs
rename to src/App/Controls/IconLabel.cs
index 146b7b017..79310f936 100644
--- a/src/App/Controls/FaLabel.cs
+++ b/src/App/Controls/IconLabel.cs
@@ -2,17 +2,17 @@
namespace Bit.App.Controls
{
- public class FaLabel : Label
+ public class IconLabel : Label
{
- public FaLabel()
+ public IconLabel()
{
switch (Device.RuntimePlatform)
{
case Device.iOS:
- FontFamily = "FontAwesome";
+ FontFamily = "bwi-font";
break;
case Device.Android:
- FontFamily = "FontAwesome.ttf#FontAwesome";
+ FontFamily = "bwi-font.ttf#bwi-font";
break;
}
}
diff --git a/src/App/Controls/SendViewCell/SendViewCell.xaml b/src/App/Controls/SendViewCell/SendViewCell.xaml
index 9103786fb..2db1a40ce 100644
--- a/src/App/Controls/SendViewCell/SendViewCell.xaml
+++ b/src/App/Controls/SendViewCell/SendViewCell.xaml
@@ -4,6 +4,7 @@
x:Class="Bit.App.Controls.SendViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
+ xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
@@ -23,7 +24,7 @@
-
-
-
-
-
-
diff --git a/src/App/Models/AppOptions.cs b/src/App/Models/AppOptions.cs
index a28a76855..7d7e2b22c 100644
--- a/src/App/Models/AppOptions.cs
+++ b/src/App/Models/AppOptions.cs
@@ -21,6 +21,7 @@ namespace Bit.App.Models
public string SaveCardCode { get; set; }
public bool IosExtension { get; set; }
public Tuple CreateSend { get; set; }
+ public bool CopyInsteadOfShareAfterSaving { get; set; }
public void SetAllFrom(AppOptions o)
{
@@ -44,6 +45,7 @@ namespace Bit.App.Models
SaveCardCode = o.SaveCardCode;
IosExtension = o.IosExtension;
CreateSend = o.CreateSend;
+ CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving;
}
}
}
diff --git a/src/App/Pages/Accounts/DeleteAccountViewModel.cs b/src/App/Pages/Accounts/DeleteAccountViewModel.cs
index 3fa0dd3e1..cab70bd38 100644
--- a/src/App/Pages/Accounts/DeleteAccountViewModel.cs
+++ b/src/App/Pages/Accounts/DeleteAccountViewModel.cs
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
+using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
@@ -12,21 +13,13 @@ namespace Bit.App.Pages
{
public class DeleteAccountViewModel : BaseViewModel
{
- readonly IApiService _apiService;
- readonly IPasswordRepromptService _passwordRepromptService;
- readonly IMessagingService _messagingService;
- readonly ICryptoService _cryptoService;
readonly IPlatformUtilsService _platformUtilsService;
- readonly IDeviceActionService _deviceActionService;
+ readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
public DeleteAccountViewModel()
{
- _apiService = ServiceContainer.Resolve("apiService");
- _passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService");
- _messagingService = ServiceContainer.Resolve("messagingService");
- _cryptoService = ServiceContainer.Resolve("cryptoService");
_platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
- _deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _verificationActionsFlowHelper = ServiceContainer.Resolve("verificationActionsFlowHelper");
PageTitle = AppResources.DeleteAccount;
}
@@ -42,18 +35,53 @@ namespace Bit.App.Pages
return;
}
- var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync();
- if (!valid)
- {
- return;
- }
+ await _verificationActionsFlowHelper
+ .Configure(VerificationFlowAction.DeleteAccount,
+ null,
+ AppResources.DeleteAccount,
+ true)
+ .ValidateAndExecuteAsync();
+ }
+ catch (System.Exception ex)
+ {
+#if !FDROID
+ Crashes.TrackError(ex);
+#endif
+ await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
+ }
+ }
+ }
+ public interface IDeleteAccountActionFlowExecutioner : IActionFlowExecutioner { }
+
+ public class DeleteAccountActionFlowExecutioner : IDeleteAccountActionFlowExecutioner
+ {
+ readonly IApiService _apiService;
+ readonly IMessagingService _messagingService;
+ readonly IPlatformUtilsService _platformUtilsService;
+ readonly IDeviceActionService _deviceActionService;
+
+ public DeleteAccountActionFlowExecutioner(IApiService apiService,
+ IMessagingService messagingService,
+ IPlatformUtilsService platformUtilsService,
+ IDeviceActionService deviceActionService)
+ {
+ _apiService = apiService;
+ _messagingService = messagingService;
+ _platformUtilsService = platformUtilsService;
+ _deviceActionService = deviceActionService;
+ }
+
+ public async Task Execute(IActionFlowParmeters parameters)
+ {
+ try
+ {
await _deviceActionService.ShowLoadingAsync(AppResources.DeletingYourAccount);
- var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(password, null);
await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest
{
- MasterPasswordHash = masterPasswordHashKey
+ MasterPasswordHash = parameters.VerificationType == Core.Enums.VerificationType.MasterPassword ? parameters.Secret : (string)null,
+ OTP = parameters.VerificationType == Core.Enums.VerificationType.OTP ? parameters.Secret : (string)null
});
await _deviceActionService.HideLoadingAsync();
diff --git a/src/App/Pages/Accounts/HomePage.xaml b/src/App/Pages/Accounts/HomePage.xaml
index 1040914ad..84c727ba6 100644
--- a/src/App/Pages/Accounts/HomePage.xaml
+++ b/src/App/Pages/Accounts/HomePage.xaml
@@ -6,6 +6,7 @@
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
+ xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:HomeViewModel"
Title="{Binding PageTitle}">
@@ -17,19 +18,19 @@
-
-
+
-
-
+
+
-
- ShowPassword ? "" : "";
+ public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string MasterPassword { get; set; }
public string Pin { get; set; }
public Action UnlockedAction { get; set; }
@@ -135,11 +137,20 @@ namespace Bit.App.Pages
// Users with key connector and without biometric or pin has no MP to unlock with
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
- if ( _usingKeyConnector && !(BiometricLock || PinLock))
+ if (_usingKeyConnector && !(BiometricLock || PinLock))
{
await _vaultTimeoutService.LogOutAsync();
+ return;
}
_email = await _userService.GetEmailAsync();
+ if (string.IsNullOrWhiteSpace(_email))
+ {
+ await _vaultTimeoutService.LogOutAsync();
+#if !FDROID
+ Crashes.TrackError(new NullReferenceException("Email not found in storage"));
+#endif
+ return;
+ }
var webVault = _environmentService.GetWebVaultUrl();
if (string.IsNullOrWhiteSpace(webVault))
{
diff --git a/src/App/Pages/Accounts/LoginPage.xaml b/src/App/Pages/Accounts/LoginPage.xaml
index 227c252cf..15be7107f 100644
--- a/src/App/Pages/Accounts/LoginPage.xaml
+++ b/src/App/Pages/Accounts/LoginPage.xaml
@@ -70,7 +70,7 @@
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding LogInCommand}" />
- ShowPassword ? "" : "";
+ public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public bool RememberEmail { get; set; }
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
diff --git a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs
index 319992292..18ed6ad46 100644
--- a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs
+++ b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs
@@ -116,7 +116,7 @@ namespace Bit.App.Pages
var redirectUri = "bitwarden://sso-callback";
var url = _apiService.IdentityBaseUrl + "/connect/authorize?" +
- "client_id=" + _platformUtilsService.IdentityClientId + "&" +
+ "client_id=" + _platformUtilsService.GetClientType().GetString() + "&" +
"redirect_uri=" + Uri.EscapeDataString(redirectUri) + "&" +
"response_type=code&scope=api%20offline_access&" +
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
diff --git a/src/App/Pages/Accounts/RegisterPage.xaml b/src/App/Pages/Accounts/RegisterPage.xaml
index 668fe28db..cce3c258d 100644
--- a/src/App/Pages/Accounts/RegisterPage.xaml
+++ b/src/App/Pages/Accounts/RegisterPage.xaml
@@ -60,7 +60,7 @@
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
-
- ShowPassword ? "" : "";
+ public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string Name { get; set; }
public string Email { get; set; }
public string MasterPassword { get; set; }
diff --git a/src/App/Pages/Accounts/SetPasswordPage.xaml b/src/App/Pages/Accounts/SetPasswordPage.xaml
index 2393ce0ab..80a24e5bc 100644
--- a/src/App/Pages/Accounts/SetPasswordPage.xaml
+++ b/src/App/Pages/Accounts/SetPasswordPage.xaml
@@ -99,7 +99,7 @@
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
-
- ShowPassword ? "" : "";
+ public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }
diff --git a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs
index f663c4217..c2f1eb6b6 100644
--- a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs
+++ b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs
@@ -328,7 +328,7 @@ namespace Bit.App.Pages
AppResources.Cancel, null, options.ToArray());
if (method == AppResources.RecoveryCodeTitle)
{
- _platformUtilsService.LaunchUri("https://help.bitwarden.com/article/lost-two-step-device/");
+ _platformUtilsService.LaunchUri("https://bitwarden.com/help/lost-two-step-device/");
}
else if (method != AppResources.Cancel && method != null)
{
diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml
index b9fd0e8ee..dfa312f1b 100644
--- a/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml
+++ b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml
@@ -97,7 +97,7 @@
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Pages/Accounts/VerificationCodePage.xaml.cs b/src/App/Pages/Accounts/VerificationCodePage.xaml.cs
new file mode 100644
index 000000000..abcac0cb1
--- /dev/null
+++ b/src/App/Pages/Accounts/VerificationCodePage.xaml.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Bit.App.Pages.Accounts
+{
+ public partial class VerificationCodePage : BaseContentPage
+ {
+ VerificationCodeViewModel _vm;
+
+ public VerificationCodePage(string mainActionText, bool mainActionStyleDanger)
+ {
+ InitializeComponent();
+
+ _vm = BindingContext as VerificationCodeViewModel;
+ _vm.Page = this;
+ _vm.MainActionText = mainActionText;
+
+ _mainActionButton.StyleClass = new[]
+ {
+ mainActionStyleDanger ? "btn-danger" : "btn-primary"
+ };
+ }
+
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+
+ if (propertyName == nameof(VerificationCodeViewModel.ShowPassword))
+ {
+ RequestFocus(_secret);
+ }
+ }
+
+ protected async override void OnAppearing()
+ {
+ base.OnAppearing();
+ await _vm.InitAsync();
+ RequestFocus(_secret);
+ }
+
+ private async void Close_Clicked(object sender, EventArgs e)
+ {
+ if (DoOnce())
+ {
+ await Navigation.PopModalAsync();
+ }
+ }
+ }
+}
diff --git a/src/App/Pages/Accounts/VerificationCodeViewModel.cs b/src/App/Pages/Accounts/VerificationCodeViewModel.cs
new file mode 100644
index 000000000..e160a535d
--- /dev/null
+++ b/src/App/Pages/Accounts/VerificationCodeViewModel.cs
@@ -0,0 +1,181 @@
+using System;
+using Bit.App.Abstractions;
+using Bit.App.Resources;
+using Bit.Core.Abstractions;
+using Bit.Core.Utilities;
+using System.Threading.Tasks;
+using Bit.Core.Exceptions;
+using Xamarin.Forms;
+using Xamarin.CommunityToolkit.ObjectModel;
+using System.Windows.Input;
+using Bit.App.Utilities;
+using Bit.Core;
+using Bit.Core.Enums;
+#if !FDROID
+using Microsoft.AppCenter.Crashes;
+#endif
+
+namespace Bit.App.Pages
+{
+ public class VerificationCodeViewModel : BaseViewModel
+ {
+ private readonly IDeviceActionService _deviceActionService;
+ private readonly IPlatformUtilsService _platformUtilsService;
+ private readonly IUserVerificationService _userVerificationService;
+ private readonly IApiService _apiService;
+ private readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
+
+ private bool _showPassword;
+ private string _secret, _mainActionText, _sendCodeStatus;
+
+ public VerificationCodeViewModel()
+ {
+ _deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
+ _userVerificationService = ServiceContainer.Resolve("userVerificationService");
+ _apiService = ServiceContainer.Resolve("apiService");
+ _verificationActionsFlowHelper = ServiceContainer.Resolve("verificationActionsFlowHelper");
+
+ PageTitle = AppResources.VerificationCode;
+
+ TogglePasswordCommand = new Command(TogglePassword);
+ MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false);
+ RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false);
+ }
+
+ public bool ShowPassword
+ {
+ get => _showPassword;
+ set => SetProperty(ref _showPassword, value,
+ additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) });
+ }
+
+ public string Secret
+ {
+ get => _secret;
+ set => SetProperty(ref _secret, value);
+ }
+
+ public string MainActionText
+ {
+ get => _mainActionText;
+ set => SetProperty(ref _mainActionText, value);
+ }
+
+ public string SendCodeStatus
+ {
+ get => _sendCodeStatus;
+ set => SetProperty(ref _sendCodeStatus, value);
+ }
+
+ public ICommand TogglePasswordCommand { get; }
+
+ public ICommand MainActionCommand { get; }
+
+ public ICommand RequestOTPCommand { get; }
+
+ public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
+
+ public void TogglePassword() => ShowPassword = !ShowPassword;
+
+ public async Task InitAsync()
+ {
+ await RequestOTPAsync();
+ }
+
+ public async Task RequestOTPAsync()
+ {
+ try
+ {
+ if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ {
+ await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
+ AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
+
+ SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
+ return;
+ }
+
+ await _deviceActionService.ShowLoadingAsync(AppResources.SendingCode);
+
+ await _apiService.PostAccountRequestOTP();
+
+ await _deviceActionService.HideLoadingAsync();
+
+ SendCodeStatus = AppResources.AVerificationCodeWasSentToYourEmail;
+
+ _platformUtilsService.ShowToast(null, null, AppResources.CodeSent);
+ }
+ catch (ApiException e)
+ {
+ await _deviceActionService.HideLoadingAsync();
+ SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
+
+ if (e?.Error != null)
+ {
+ await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), AppResources.AnErrorHasOccurred);
+ }
+ }
+ catch (Exception ex)
+ {
+#if !FDROID
+ Crashes.TrackError(ex);
+#endif
+ await _deviceActionService.HideLoadingAsync();
+ SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
+ }
+ }
+
+ private async Task MainActionAsync()
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(Secret))
+ {
+ await _platformUtilsService.ShowDialogAsync(AppResources.EnterTheVerificationCodeThatWasSentToYourEmail, AppResources.AnErrorHasOccurred);
+ return;
+ }
+
+ if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ {
+ await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
+ AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
+ return;
+ }
+
+ await _deviceActionService.ShowLoadingAsync(AppResources.Verifying);
+
+ if (!await _userVerificationService.VerifyUser(Secret, VerificationType.OTP))
+ {
+ await _deviceActionService.HideLoadingAsync();
+ return;
+ }
+
+ await _deviceActionService.HideLoadingAsync();
+
+ var parameters = _verificationActionsFlowHelper.GetParameters();
+ parameters.Secret = Secret;
+ parameters.VerificationType = VerificationType.OTP;
+ await _verificationActionsFlowHelper.ExecuteAsync(parameters);
+
+ Secret = string.Empty;
+ }
+ catch (ApiException e)
+ {
+ await _deviceActionService.HideLoadingAsync();
+ if (e?.Error != null)
+ {
+ await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
+ AppResources.AnErrorHasOccurred);
+ }
+ }
+ catch (Exception ex)
+ {
+#if !FDROID
+ Crashes.TrackError(ex);
+#endif
+ await _deviceActionService.HideLoadingAsync();
+ }
+ }
+ }
+}
diff --git a/src/App/Pages/BaseContentPage.cs b/src/App/Pages/BaseContentPage.cs
index eaf592bf6..681e36820 100644
--- a/src/App/Pages/BaseContentPage.cs
+++ b/src/App/Pages/BaseContentPage.cs
@@ -30,9 +30,17 @@ namespace Bit.App.Pages
public DateTime? LastPageAction { get; set; }
+ public bool IsThemeDirty { get; set; }
+
protected override void OnAppearing()
{
base.OnAppearing();
+
+ if (IsThemeDirty)
+ {
+ UpdateOnThemeChanged();
+ }
+
SaveActivity();
}
@@ -123,5 +131,11 @@ namespace Bit.App.Pages
SetServices();
_storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime());
}
+
+ public virtual Task UpdateOnThemeChanged()
+ {
+ IsThemeDirty = false;
+ return Task.CompletedTask;
+ }
}
}
diff --git a/src/App/Pages/Generator/GeneratorHistoryPage.xaml b/src/App/Pages/Generator/GeneratorHistoryPage.xaml
index 74edf0f7d..b110b1b70 100644
--- a/src/App/Pages/Generator/GeneratorHistoryPage.xaml
+++ b/src/App/Pages/Generator/GeneratorHistoryPage.xaml
@@ -7,6 +7,7 @@
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:domain="clr-namespace:Bit.Core.Models.Domain;assembly=BitwardenCore"
+ xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:GeneratorHistoryPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
@@ -75,9 +76,9 @@
Grid.Row="1"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}" />
- {
await _vm.InitAsync();
});
@@ -59,5 +62,12 @@ namespace Bit.App.Pages
await _vm.ClearAsync();
}
}
+
+ public override async Task UpdateOnThemeChanged()
+ {
+ await base.UpdateOnThemeChanged();
+
+ await _vm?.UpdateOnThemeChanged();
+ }
}
}
diff --git a/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs b/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs
index f4b78c5d4..e92aedde4 100644
--- a/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs
+++ b/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs
@@ -1,9 +1,12 @@
-using Bit.App.Resources;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
-using System.Collections.Generic;
-using System.Threading.Tasks;
+#if !FDROID
+using Microsoft.AppCenter.Crashes;
+#endif
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -19,8 +22,7 @@ namespace Bit.App.Pages
public GeneratorHistoryPageViewModel()
{
_platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
- _passwordGenerationService = ServiceContainer.Resolve(
- "passwordGenerationService");
+ _passwordGenerationService = ServiceContainer.Resolve("passwordGenerationService");
_clipboardService = ServiceContainer.Resolve("clipboardService");
PageTitle = AppResources.PasswordHistory;
@@ -57,5 +59,21 @@ namespace Bit.App.Pages
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
+
+ public async Task UpdateOnThemeChanged()
+ {
+ try
+ {
+ await Device.InvokeOnMainThreadAsync(() => History.ResetWithRange(new List()));
+
+ await InitAsync();
+ }
+ catch (System.Exception ex)
+ {
+#if !FDROID
+ Crashes.TrackError(ex);
+#endif
+ }
+ }
}
}
diff --git a/src/App/Pages/Generator/GeneratorPage.xaml b/src/App/Pages/Generator/GeneratorPage.xaml
index 51f8b99c8..14c32e2c4 100644
--- a/src/App/Pages/Generator/GeneratorPage.xaml
+++ b/src/App/Pages/Generator/GeneratorPage.xaml
@@ -63,6 +63,7 @@
().SetUpdateMode(UpdateMode.WhenFinished);
+ _typePicker.On().SetUpdateMode(UpdateMode.WhenFinished);
}
}
@@ -61,18 +61,19 @@ namespace Bit.App.Pages
protected async override void OnAppearing()
{
base.OnAppearing();
+
+ lblPassword.IsVisible = true;
+
if (!_fromTabPage)
{
await InitAsync();
}
- _broadcasterService.Subscribe(nameof(GeneratorPage), async (message) =>
+
+ _broadcasterService.Subscribe(nameof(GeneratorPage), (message) =>
{
if (message.Command == "updatedTheme")
{
- Device.BeginInvokeOnMainThread(() =>
- {
- _vm.RedrawPassword();
- });
+ Device.BeginInvokeOnMainThread(() => _vm.RedrawPassword());
}
});
}
@@ -80,6 +81,9 @@ namespace Bit.App.Pages
protected override void OnDisappearing()
{
base.OnDisappearing();
+
+ lblPassword.IsVisible = false;
+
_broadcasterService.Unsubscribe(nameof(GeneratorPage));
}
@@ -141,5 +145,12 @@ namespace Bit.App.Pages
await Navigation.PopModalAsync();
}
}
+
+ public override async Task UpdateOnThemeChanged()
+ {
+ await base.UpdateOnThemeChanged();
+
+ await Device.InvokeOnMainThreadAsync(() => _vm?.RedrawPassword());
+ }
}
}
diff --git a/src/App/Pages/Send/SendAddEditPage.xaml b/src/App/Pages/Send/SendAddEditPage.xaml
index 9568a78d7..b72250979 100644
--- a/src/App/Pages/Send/SendAddEditPage.xaml
+++ b/src/App/Pages/Send/SendAddEditPage.xaml
@@ -8,6 +8,7 @@
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
xmlns:effects="clr-namespace:Bit.App.Effects"
+ xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:SendAddEditPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
@@ -234,9 +235,10 @@
Margin="10,0,0,0" />
-
+
@@ -255,16 +257,16 @@
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
Margin="0" />
-
-
-
- {
- if (message.Command == "selectFileResult")
+ if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
{
- Device.BeginInvokeOnMainThread(() =>
- {
- var data = message.Data as Tuple;
- _vm.FileData = data.Item1;
- _vm.FileName = data.Item2;
- });
+ await _vaultTimeoutService.CheckVaultTimeoutAsync();
}
- });
- await LoadOnAppearedAsync(_scrollView, true, async () =>
- {
- var success = await _vm.LoadAsync();
- if (!success)
+ if (await _vaultTimeoutService.IsLockedAsync())
{
- await Navigation.PopModalAsync();
return;
}
- await HandleCreateRequest();
- if (!_vm.EditMode && string.IsNullOrWhiteSpace(_vm.Send?.Name))
+ await _vm.InitAsync();
+ _broadcasterService.Subscribe(nameof(SendAddEditPage), message =>
{
- RequestFocus(_nameEntry);
- }
- AdjustToolbar();
- });
+ if (message.Command == "selectFileResult")
+ {
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ var data = message.Data as Tuple;
+ _vm.FileData = data.Item1;
+ _vm.FileName = data.Item2;
+ });
+ }
+ });
+
+ await LoadOnAppearedAsync(_scrollView, true, async () =>
+ {
+ var success = await _vm.LoadAsync();
+ if (!success)
+ {
+ await CloseAsync();
+ return;
+ }
+ await HandleCreateRequest();
+ if (!_vm.EditMode && string.IsNullOrWhiteSpace(_vm.Send?.Name))
+ {
+ RequestFocus(_nameEntry);
+ }
+ AdjustToolbar();
+ });
+ }
+ catch (Exception ex)
+ {
+#if !FDROID
+ Crashes.TrackError(ex);
+#endif
+ await CloseAsync();
+ }
+ }
+
+ private async Task CloseAsync()
+ {
+ if (OnClose is null)
+ {
+ await Navigation.PopModalAsync();
+ }
+ else
+ {
+ OnClose();
+ }
}
protected override bool OnBackButtonPressed()
@@ -200,7 +230,11 @@ namespace Bit.App.Pages
{
if (DoOnce())
{
- await _vm.SubmitAsync();
+ var submitted = await _vm.SubmitAsync();
+ if (submitted)
+ {
+ AfterSubmit?.Invoke();
+ }
}
}
@@ -234,7 +268,7 @@ namespace Bit.App.Pages
{
if (await _vm.DeleteAsync())
{
- await Navigation.PopModalAsync();
+ await CloseAsync();
}
}
}
@@ -274,7 +308,7 @@ namespace Bit.App.Pages
{
if (await _vm.DeleteAsync())
{
- await Navigation.PopModalAsync();
+ await CloseAsync();
}
}
}
@@ -283,7 +317,7 @@ namespace Bit.App.Pages
{
if (DoOnce())
{
- await Navigation.PopModalAsync();
+ await CloseAsync();
}
}
@@ -306,6 +340,7 @@ namespace Bit.App.Pages
}
_vm.IsAddFromShare = true;
+ _vm.CopyInsteadOfShareAfterSaving = _appOptions.CopyInsteadOfShareAfterSaving;
var name = _appOptions.CreateSend.Item2;
_vm.Send.Name = name;
diff --git a/src/App/Pages/Send/SendAddEditPageViewModel.cs b/src/App/Pages/Send/SendAddEditPageViewModel.cs
index 73aea806d..e0dac3c35 100644
--- a/src/App/Pages/Send/SendAddEditPageViewModel.cs
+++ b/src/App/Pages/Send/SendAddEditPageViewModel.cs
@@ -4,11 +4,15 @@ using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
+using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
+#if !FDROID
+using Microsoft.AppCenter.Crashes;
+#endif
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -45,6 +49,7 @@ namespace Bit.App.Pages
};
private bool _disableHideEmail;
private bool _sendOptionsPolicyInEffect;
+ private bool _copyInsteadOfShareAfterSaving;
public SendAddEditPageViewModel()
{
@@ -96,6 +101,7 @@ namespace Bit.App.Pages
public bool ShareOnSave { get; set; }
public bool DisableHideEmailControl { get; set; }
public bool IsAddFromShare { get; set; }
+ public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
public List> TypeOptions { get; }
public List> DeletionTypeOptions { get; }
public List> ExpirationTypeOptions { get; }
@@ -174,6 +180,15 @@ namespace Bit.App.Pages
}
}
}
+ public bool CopyInsteadOfShareAfterSaving
+ {
+ get => _copyInsteadOfShareAfterSaving;
+ set
+ {
+ SetProperty(ref _copyInsteadOfShareAfterSaving, value);
+ TriggerPropertyChanged(nameof(ShareOnSaveText));
+ }
+ }
public SendView Send
{
get => _send;
@@ -215,7 +230,7 @@ namespace Bit.App.Pages
public bool IsFile => Send?.Type == SendType.File;
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
- public string ShowPasswordIcon => ShowPassword ? "" : "";
+ public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public async Task InitAsync()
{
@@ -396,25 +411,36 @@ namespace Bit.App.Pages
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
}
- if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
+ if (!CopyInsteadOfShareAfterSaving)
{
- _deviceActionService.CloseMainApp();
+ await CloseAsync();
}
- else
- {
- await Page.Navigation.PopModalAsync();
- }
-
+
if (ShareOnSave)
{
var savedSend = await _sendService.GetAsync(sendId);
if (savedSend != null)
{
var savedSendView = await savedSend.DecryptAsync();
- await AppHelpers.ShareSendUrlAsync(savedSendView);
+ if (CopyInsteadOfShareAfterSaving)
+ {
+ await AppHelpers.CopySendUrlAsync(savedSendView);
+
+ // wait so that the user sees the message before the view gets dismissed
+ await Task.Delay(1300);
+ }
+ else
+ {
+ await AppHelpers.ShareSendUrlAsync(savedSendView);
+ }
}
}
-
+
+ if (CopyInsteadOfShareAfterSaving)
+ {
+ await CloseAsync();
+ }
+
return true;
}
catch (ApiException e)
@@ -426,9 +452,37 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred);
}
}
+ catch (Exception ex)
+ {
+ await _deviceActionService.HideLoadingAsync();
+#if !FDROID
+ Crashes.TrackError(ex);
+#endif
+ await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
+ }
return false;
}
+ private async Task CloseAsync()
+ {
+ if (IsAddFromShare)
+ {
+ if (Device.RuntimePlatform == Device.Android)
+ {
+ _deviceActionService.CloseMainApp();
+ return;
+ }
+
+ if (Page is SendAddEditPage sendPage && sendPage.OnClose != null)
+ {
+ sendPage.OnClose();
+ return;
+ }
+ }
+
+ await Page.Navigation.PopModalAsync();
+ }
+
public async Task RemovePasswordAsync()
{
return await AppHelpers.RemoveSendPasswordAsync(SendId);
@@ -454,14 +508,7 @@ namespace Bit.App.Pages
if (!SendEnabled)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendDisabledWarning);
- if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
- {
- _deviceActionService.CloseMainApp();
- }
- else
- {
- await Page.Navigation.PopModalAsync();
- }
+ await CloseAsync();
return;
}
if (Send != null)
diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml
index 0e2f2ca05..c83b6ad10 100644
--- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml
+++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml
@@ -51,14 +51,14 @@
x:DataType="pages:SendGroupingsPageListItem">
-
-
+
-
-
+
+
diff --git a/src/Core/Enums/ClientType.cs b/src/Core/Enums/ClientType.cs
new file mode 100644
index 000000000..f6d3fd9c2
--- /dev/null
+++ b/src/Core/Enums/ClientType.cs
@@ -0,0 +1,36 @@
+namespace Bit.Core.Enums
+{
+ public enum ClientType: byte
+ {
+ Web = 1,
+ Browser = 2,
+ Desktop = 3,
+ Mobile = 4,
+ Cli = 5,
+ DirectoryConnector = 6,
+ }
+
+ public static class ClientTypeExtensions
+ {
+ public static string GetString(this ClientType me)
+ {
+ switch (me)
+ {
+ case ClientType.Web:
+ return "web";
+ case ClientType.Browser:
+ return "browser";
+ case ClientType.Desktop:
+ return "desktop";
+ case ClientType.Mobile:
+ return "mobile";
+ case ClientType.Cli:
+ return "cli";
+ case ClientType.DirectoryConnector:
+ return "connector";
+ default:
+ return "";
+ }
+ }
+ }
+}
diff --git a/src/Core/Models/Request/DeleteAccountRequest.cs b/src/Core/Models/Request/DeleteAccountRequest.cs
index 7776d890e..8eb38d347 100644
--- a/src/Core/Models/Request/DeleteAccountRequest.cs
+++ b/src/Core/Models/Request/DeleteAccountRequest.cs
@@ -3,5 +3,7 @@
public class DeleteAccountRequest
{
public string MasterPasswordHash { get; set; }
+
+ public string OTP { get; set; }
}
}
diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs
index e73216aea..c0b11a6a1 100644
--- a/src/Core/Services/ApiService.cs
+++ b/src/Core/Services/ApiService.cs
@@ -1,4 +1,5 @@
using Bit.Core.Abstractions;
+using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
@@ -37,6 +38,8 @@ namespace Bit.Core.Services
_logoutCallbackAsync = logoutCallbackAsync;
var device = (int)_platformUtilsService.GetDevice();
_httpClient.DefaultRequestHeaders.Add("Device-Type", device.ToString());
+ _httpClient.DefaultRequestHeaders.Add("Bitwarden-Client-Name", _platformUtilsService.GetClientType().GetString());
+ _httpClient.DefaultRequestHeaders.Add("Bitwarden-Client-Version", _platformUtilsService.GetApplicationVersion());
if (!string.IsNullOrWhiteSpace(customUserAgent))
{
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(customUserAgent);
@@ -87,7 +90,7 @@ namespace Bit.Core.Services
Version = new Version(1, 0),
RequestUri = new Uri(string.Concat(IdentityBaseUrl, "/connect/token")),
Method = HttpMethod.Post,
- Content = new FormUrlEncodedContent(request.ToIdentityToken(_platformUtilsService.IdentityClientId))
+ Content = new FormUrlEncodedContent(request.ToIdentityToken(_platformUtilsService.GetClientType().GetString()))
};
requestMessage.Headers.Add("Accept", "application/json");
request.AlterIdentityTokenHeaders(requestMessage.Headers);
@@ -180,13 +183,13 @@ namespace Bit.Core.Services
public Task PostAccountRequestOTP()
{
- return SendAsync