diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 1ac400e27..7abf7d6dd 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -152,6 +152,7 @@
+
diff --git a/src/Android/Receivers/NotificationDismissReceiver.cs b/src/Android/Receivers/NotificationDismissReceiver.cs
new file mode 100644
index 000000000..43f69ea62
--- /dev/null
+++ b/src/Android/Receivers/NotificationDismissReceiver.cs
@@ -0,0 +1,41 @@
+using Android.Content;
+using Bit.App.Abstractions;
+using Bit.App.Models;
+using Bit.App.Services;
+using Bit.Core;
+using Bit.Core.Abstractions;
+using Bit.Core.Services;
+using Bit.Core.Utilities;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using CoreConstants = Bit.Core.Constants;
+
+namespace Bit.Droid.Receivers
+{
+ [BroadcastReceiver(Name = Constants.PACKAGE_NAME + "." + nameof(NotificationDismissReceiver), Exported = false)]
+ public class NotificationDismissReceiver : BroadcastReceiver
+ {
+ private readonly LazyResolve _pushNotificationListenerService = new LazyResolve();
+ private readonly LazyResolve _logger = new LazyResolve();
+
+ public override void OnReceive(Context context, Intent intent)
+ {
+ try
+ {
+ if (intent?.GetStringExtra(CoreConstants.NotificationData) is string notificationDataJson)
+ {
+ var notificationType = JToken.Parse(notificationDataJson).SelectToken(CoreConstants.NotificationDataType);
+ if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
+ {
+ _pushNotificationListenerService.Value.OnNotificationDismissed(JsonConvert.DeserializeObject(notificationDataJson)).FireAndForget();
+ }
+ }
+ }
+ catch (System.Exception ex)
+ {
+ _logger.Value.Exception(ex);
+ }
+ }
+ }
+}
+
diff --git a/src/Android/Services/AndroidPushNotificationService.cs b/src/Android/Services/AndroidPushNotificationService.cs
index 03f8ad672..6c5383f8f 100644
--- a/src/Android/Services/AndroidPushNotificationService.cs
+++ b/src/Android/Services/AndroidPushNotificationService.cs
@@ -10,9 +10,12 @@ using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.Core;
using Bit.Core.Abstractions;
+using Bit.Droid.Receivers;
using Bit.Droid.Utilities;
using Newtonsoft.Json;
using Xamarin.Forms;
+using static Xamarin.Essentials.Platform;
+using Intent = Android.Content.Intent;
namespace Bit.Droid.Services
{
@@ -79,16 +82,21 @@ namespace Bit.Droid.Services
var context = Android.App.Application.Context;
var intent = new Intent(context, typeof(MainActivity));
- intent.PutExtra(Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
-
+ intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
- var builder = new NotificationCompat.Builder(context, Core.Constants.AndroidNotificationChannelId)
+
+ var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
+ deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
+ var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
+
+ var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId)
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetSmallIcon(Resource.Drawable.ic_notification)
.SetColor((int)Android.Graphics.Color.White)
+ .SetDeleteIntent(deletePendingIntent)
.SetAutoCancel(true);
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
diff --git a/src/App/Abstractions/IPushNotificationListenerService.cs b/src/App/Abstractions/IPushNotificationListenerService.cs
index f5d2cb39f..fdbb6ca88 100644
--- a/src/App/Abstractions/IPushNotificationListenerService.cs
+++ b/src/App/Abstractions/IPushNotificationListenerService.cs
@@ -11,6 +11,7 @@ namespace Bit.App.Abstractions
void OnUnregistered(string device);
void OnError(string message, string device);
Task OnNotificationTapped(BaseNotificationData data);
+ Task OnNotificationDismissed(BaseNotificationData data);
bool ShouldShowNotification();
}
}
diff --git a/src/App/Services/NoopPushNotificationListenerService.cs b/src/App/Services/NoopPushNotificationListenerService.cs
index b8b6b2890..c70c43479 100644
--- a/src/App/Services/NoopPushNotificationListenerService.cs
+++ b/src/App/Services/NoopPushNotificationListenerService.cs
@@ -34,5 +34,10 @@ namespace Bit.App.Services
{
return Task.FromResult(0);
}
+
+ public Task OnNotificationDismissed(BaseNotificationData data)
+ {
+ return Task.FromResult(0);
+ }
}
}
diff --git a/src/App/Services/PushNotificationListenerService.cs b/src/App/Services/PushNotificationListenerService.cs
index b2a58f627..5b5f38b90 100644
--- a/src/App/Services/PushNotificationListenerService.cs
+++ b/src/App/Services/PushNotificationListenerService.cs
@@ -26,20 +26,18 @@ namespace Bit.App.Services
const string TAG = "##PUSH NOTIFICATIONS";
private bool _showNotification;
- private bool _resolved;
- private ISyncService _syncService;
- private IStateService _stateService;
- private IAppIdService _appIdService;
- private IApiService _apiService;
- private IMessagingService _messagingService;
- private IPushNotificationService _pushNotificationService;
- private ILogger _logger;
+ private LazyResolve _syncService = new LazyResolve();
+ private LazyResolve _stateService = new LazyResolve();
+ private LazyResolve _appIdService = new LazyResolve();
+ private LazyResolve _apiService = new LazyResolve();
+ private LazyResolve _messagingService = new LazyResolve();
+ private LazyResolve _pushNotificationService = new LazyResolve();
+ private LazyResolve _logger = new LazyResolve();
public async Task OnMessageAsync(JObject value, string deviceType)
{
Debug.WriteLine($"{TAG} OnMessageAsync called");
- Resolve();
if (value == null)
{
return;
@@ -65,14 +63,14 @@ namespace Bit.App.Services
Debug.WriteLine($"{TAG} - Notification object created: t:{notification?.Type} - p:{notification?.Payload}");
- var appId = await _appIdService.GetAppIdAsync();
+ var appId = await _appIdService.Value.GetAppIdAsync();
if (notification?.Payload == null || notification.ContextId == appId)
{
return;
}
- var myUserId = await _stateService.GetActiveUserIdAsync();
- var isAuthenticated = await _stateService.IsAuthenticatedAsync();
+ var myUserId = await _stateService.Value.GetActiveUserIdAsync();
+ var isAuthenticated = await _stateService.Value.IsAuthenticatedAsync();
switch (notification.Type)
{
case NotificationType.SyncCipherUpdate:
@@ -81,7 +79,7 @@ namespace Bit.App.Services
notification.Payload);
if (isAuthenticated && cipherCreateUpdateMessage.UserId == myUserId)
{
- await _syncService.SyncUpsertCipherAsync(cipherCreateUpdateMessage,
+ await _syncService.Value.SyncUpsertCipherAsync(cipherCreateUpdateMessage,
notification.Type == NotificationType.SyncCipherUpdate);
}
break;
@@ -91,7 +89,7 @@ namespace Bit.App.Services
notification.Payload);
if (isAuthenticated && folderCreateUpdateMessage.UserId == myUserId)
{
- await _syncService.SyncUpsertFolderAsync(folderCreateUpdateMessage,
+ await _syncService.Value.SyncUpsertFolderAsync(folderCreateUpdateMessage,
notification.Type == NotificationType.SyncFolderUpdate);
}
break;
@@ -101,7 +99,7 @@ namespace Bit.App.Services
notification.Payload);
if (isAuthenticated && loginDeleteMessage.UserId == myUserId)
{
- await _syncService.SyncDeleteCipherAsync(loginDeleteMessage);
+ await _syncService.Value.SyncDeleteCipherAsync(loginDeleteMessage);
}
break;
case NotificationType.SyncFolderDelete:
@@ -109,7 +107,7 @@ namespace Bit.App.Services
notification.Payload);
if (isAuthenticated && folderDeleteMessage.UserId == myUserId)
{
- await _syncService.SyncDeleteFolderAsync(folderDeleteMessage);
+ await _syncService.Value.SyncDeleteFolderAsync(folderDeleteMessage);
}
break;
case NotificationType.SyncCiphers:
@@ -117,27 +115,27 @@ namespace Bit.App.Services
case NotificationType.SyncSettings:
if (isAuthenticated)
{
- await _syncService.FullSyncAsync(false);
+ await _syncService.Value.FullSyncAsync(false);
}
break;
case NotificationType.SyncOrgKeys:
if (isAuthenticated)
{
- await _apiService.RefreshIdentityTokenAsync();
- await _syncService.FullSyncAsync(true);
+ await _apiService.Value.RefreshIdentityTokenAsync();
+ await _syncService.Value.FullSyncAsync(true);
}
break;
case NotificationType.LogOut:
if (isAuthenticated)
{
- _messagingService.Send("logout");
+ _messagingService.Value.Send("logout");
}
break;
case NotificationType.AuthRequest:
var passwordlessLoginMessage = JsonConvert.DeserializeObject(notification.Payload);
// if the user has not enabled passwordless logins ignore requests
- if (!await _stateService.GetApprovePasswordlessLoginsAsync(passwordlessLoginMessage?.UserId))
+ if (!await _stateService.Value.GetApprovePasswordlessLoginsAsync(passwordlessLoginMessage?.UserId))
{
return;
}
@@ -148,8 +146,8 @@ namespace Bit.App.Services
return;
}
- await _stateService.SetPasswordlessLoginNotificationAsync(passwordlessLoginMessage, passwordlessLoginMessage?.UserId);
- var userEmail = await _stateService.GetEmailAsync(passwordlessLoginMessage?.UserId);
+ await _stateService.Value.SetPasswordlessLoginNotificationAsync(passwordlessLoginMessage, passwordlessLoginMessage?.UserId);
+ var userEmail = await _stateService.Value.GetEmailAsync(passwordlessLoginMessage?.UserId);
var notificationData = new PasswordlessNotificationData()
{
@@ -158,8 +156,8 @@ namespace Bit.App.Services
UserEmail = userEmail,
};
- _pushNotificationService.SendLocalNotification(AppResources.LogInRequested, String.Format(AppResources.ConfimLogInAttempForX, userEmail), notificationData);
- _messagingService.Send("passwordlessLoginRequest", passwordlessLoginMessage);
+ _pushNotificationService.Value.SendLocalNotification(AppResources.LogInRequested, String.Format(AppResources.ConfimLogInAttempForX, userEmail), notificationData);
+ _messagingService.Value.Send("passwordlessLoginRequest", passwordlessLoginMessage);
break;
default:
break;
@@ -168,31 +166,30 @@ namespace Bit.App.Services
public async Task OnRegisteredAsync(string token, string deviceType)
{
- Resolve();
Debug.WriteLine($"{TAG} - Device Registered - Token : {token}");
- var isAuthenticated = await _stateService.IsAuthenticatedAsync();
+ var isAuthenticated = await _stateService.Value.IsAuthenticatedAsync();
if (!isAuthenticated)
{
Debug.WriteLine($"{TAG} - not auth");
return;
}
- var appId = await _appIdService.GetAppIdAsync();
+ var appId = await _appIdService.Value.GetAppIdAsync();
try
{
#if DEBUG
- await _stateService.SetPushInstallationRegistrationErrorAsync(null);
+ await _stateService.Value.SetPushInstallationRegistrationErrorAsync(null);
#endif
- await _apiService.PutDeviceTokenAsync(appId,
+ await _apiService.Value.PutDeviceTokenAsync(appId,
new Core.Models.Request.DeviceTokenRequest { PushToken = token });
Debug.WriteLine($"{TAG} Registered device with server.");
- await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow);
+ await _stateService.Value.SetPushLastRegistrationDateAsync(DateTime.UtcNow);
if (deviceType == Device.Android)
{
- await _stateService.SetPushCurrentTokenAsync(token);
+ await _stateService.Value.SetPushCurrentTokenAsync(token);
}
}
#if DEBUG
@@ -200,11 +197,11 @@ namespace Bit.App.Services
{
Debug.WriteLine($"{TAG} Failed to register device.");
- await _stateService.SetPushInstallationRegistrationErrorAsync(apiEx.Error?.Message);
+ await _stateService.Value.SetPushInstallationRegistrationErrorAsync(apiEx.Error?.Message);
}
catch (Exception e)
{
- await _stateService.SetPushInstallationRegistrationErrorAsync(e.Message);
+ await _stateService.Value.SetPushInstallationRegistrationErrorAsync(e.Message);
throw;
}
#else
@@ -226,22 +223,44 @@ namespace Bit.App.Services
public async Task OnNotificationTapped(BaseNotificationData data)
{
- Resolve();
try
{
if (data is PasswordlessNotificationData passwordlessNotificationData)
{
- var notificationUserId = await _stateService.GetUserIdAsync(passwordlessNotificationData.UserEmail);
+ var notificationUserId = await _stateService.Value.GetUserIdAsync(passwordlessNotificationData.UserEmail);
if (notificationUserId != null)
{
- await _stateService.SetActiveUserAsync(notificationUserId);
- _messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
+ await _stateService.Value.SetActiveUserAsync(notificationUserId);
+ _messagingService.Value.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
}
}
}
catch (Exception ex)
{
- _logger.Exception(ex);
+ _logger.Value.Exception(ex);
+ }
+ }
+
+ public async Task OnNotificationDismissed(BaseNotificationData data)
+ {
+ try
+ {
+ if (data is PasswordlessNotificationData passwordlessNotificationData)
+ {
+ var notificationUserId = await _stateService.Value.GetUserIdAsync(passwordlessNotificationData.UserEmail);
+ if (notificationUserId != null)
+ {
+ var savedNotification = await _stateService.Value.GetPasswordlessLoginNotificationAsync(notificationUserId);
+ if (savedNotification != null)
+ {
+ await _stateService.Value.SetPasswordlessLoginNotificationAsync(null, notificationUserId);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.Value.Exception(ex);
}
}
@@ -249,22 +268,6 @@ namespace Bit.App.Services
{
return _showNotification;
}
-
- private void Resolve()
- {
- if (_resolved)
- {
- return;
- }
- _syncService = ServiceContainer.Resolve("syncService");
- _stateService = ServiceContainer.Resolve("stateService");
- _appIdService = ServiceContainer.Resolve("appIdService");
- _apiService = ServiceContainer.Resolve("apiService");
- _messagingService = ServiceContainer.Resolve("messagingService");
- _pushNotificationService = ServiceContainer.Resolve();
- _logger = ServiceContainer.Resolve();
- _resolved = true;
- }
}
}
#endif
diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs
index 7250c02b8..903ef5e7e 100644
--- a/src/Core/Constants.cs
+++ b/src/Core/Constants.cs
@@ -33,7 +33,7 @@
public const string PasswordlessNotificationId = "26072022";
public const string AndroidNotificationChannelId = "general_notification_channel";
public const string NotificationData = "notificationData";
- public const string NotificationDataType = "NotificationType";
+ public const string NotificationDataType = "Type";
public const int SelectFileRequestCode = 42;
public const int SelectFilePermissionRequestCode = 43;
public const int SaveFileRequestCode = 44;
diff --git a/src/iOS/Services/iOSPushNotificationHandler.cs b/src/iOS/Services/iOSPushNotificationHandler.cs
index de484d5d2..b19fe17e0 100644
--- a/src/iOS/Services/iOSPushNotificationHandler.cs
+++ b/src/iOS/Services/iOSPushNotificationHandler.cs
@@ -96,23 +96,35 @@ namespace Bit.iOS.Services
public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
Debug.WriteLine($"{TAG} DidReceiveNotificationResponse {response?.Notification?.Request?.Content?.UserInfo}");
-
- if (response.IsDefaultAction && response?.Notification?.Request?.Content?.UserInfo != null)
+ if ((response?.Notification?.Request?.Content?.UserInfo) == null)
{
- var userInfo = response?.Notification?.Request?.Content?.UserInfo;
- OnMessageReceived(userInfo);
+ completionHandler();
+ return;
+ }
- if (userInfo.TryGetValue(NSString.FromObject(Constants.NotificationData), out NSObject nsObject))
+ var userInfo = response?.Notification?.Request?.Content?.UserInfo;
+ OnMessageReceived(userInfo);
+
+ if (userInfo.TryGetValue(NSString.FromObject(Constants.NotificationData), out NSObject nsObject))
+ {
+ var token = JToken.Parse(NSString.FromObject(nsObject).ToString());
+ var typeToken = token.SelectToken(Constants.NotificationDataType);
+ if (response.IsDefaultAction)
{
- var token = JToken.Parse(NSString.FromObject(nsObject).ToString());
- var typeToken = token.SelectToken(Constants.NotificationDataType);
if (typeToken.ToString() == PasswordlessNotificationData.TYPE)
{
_pushNotificationListenerService.OnNotificationTapped(token.ToObject());
}
}
+ else if (response.IsDismissAction)
+ {
+ if (typeToken.ToString() == PasswordlessNotificationData.TYPE)
+ {
+ _pushNotificationListenerService.OnNotificationDismissed(token.ToObject());
+ }
+ }
}
-
+
// Inform caller it has been handled
completionHandler();
}