diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 22adaab7b..a231e42ee 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -164,14 +164,6 @@
..\..\packages\Xam.Plugins.Settings.3.0.1\lib\MonoAndroid10\Plugin.Settings.Abstractions.dll
-
- ..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\MonoAndroid10\PushNotification.Plugin.dll
- True
-
-
- ..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\MonoAndroid10\PushNotification.Plugin.Abstractions.dll
- True
-
..\..\packages\SimpleInjector.4.0.8\lib\netstandard1.3\SimpleInjector.dll
@@ -325,6 +317,7 @@
+
diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs
index bef49ba39..cff22d964 100644
--- a/src/Android/MainApplication.cs
+++ b/src/Android/MainApplication.cs
@@ -12,8 +12,6 @@ using Plugin.Connectivity;
using Plugin.CurrentActivity;
using Plugin.Fingerprint;
using Plugin.Settings;
-using PushNotification.Plugin;
-using PushNotification.Plugin.Abstractions;
using XLabs.Ioc;
using System.Threading.Tasks;
using Plugin.Settings.Abstractions;
diff --git a/src/Android/Services/PushNotificationImplementation.cs b/src/Android/Services/PushNotificationImplementation.cs
new file mode 100644
index 000000000..72a2e3aa3
--- /dev/null
+++ b/src/Android/Services/PushNotificationImplementation.cs
@@ -0,0 +1,564 @@
+using System;
+using System.Collections.Generic;
+using Android.App;
+using Android.Content;
+using Android.OS;
+using Bit.App.Abstractions;
+using Bit.App.Utilities;
+using System.Threading;
+using Xamarin.Forms;
+using Android.Gms.Gcm.Iid;
+using Android.Gms.Gcm;
+using Java.IO;
+using Newtonsoft.Json.Linq;
+using Android.Support.V4.App;
+using Android.Media;
+using Newtonsoft.Json;
+
+namespace Bit.Android.Services
+{
+ public class PushNotificationImplementation : IPushNotification
+ {
+ private const string GcmPreferencesKey = "GCMPreferences";
+ private const string Tag = "PushNotification";
+
+ internal static IPushNotificationListener Listener { get; set; }
+ public string Token => GetRegistrationId();
+
+ public void Register()
+ {
+
+ System.Diagnostics.Debug.WriteLine(
+ $"{PushNotificationKey.DomainName} - Register - Registering push notifications");
+
+ if(string.IsNullOrEmpty(CrossPushNotification.SenderId))
+ {
+ System.Diagnostics.Debug.WriteLine(
+ $"{PushNotificationKey.DomainName} - Register - SenderId is missing.");
+
+ CrossPushNotification.PushNotificationListener.OnError(
+ $"{PushNotificationKey.DomainName} - Register - Sender Id is missing.", Device.Android);
+ }
+ else
+ {
+ System.Diagnostics.Debug.WriteLine(
+ $"{PushNotificationKey.DomainName} - Register - Registering for Push Notifications");
+
+ ThreadPool.QueueUserWorkItem(state =>
+ {
+ try
+ {
+ Intent intent = new Intent(global::Android.App.Application.Context,
+ typeof(PushNotificationRegistrationIntentService));
+
+ global::Android.App.Application.Context.StartService(intent);
+ }
+ catch(Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"{Tag} - Error : {ex.Message}");
+ CrossPushNotification.PushNotificationListener.OnError($"{Tag} - Register - {ex.Message}",
+ Device.Android);
+ }
+ });
+ }
+ }
+
+ public void Unregister()
+ {
+ ThreadPool.QueueUserWorkItem(state =>
+ {
+ System.Diagnostics.Debug.WriteLine(
+ $"{PushNotificationKey.DomainName} - Unregister - Unregistering push notifications");
+ try
+ {
+ InstanceID instanceID = InstanceID.GetInstance(global::Android.App.Application.Context);
+ instanceID.DeleteToken(CrossPushNotification.SenderId, GoogleCloudMessaging.InstanceIdScope);
+
+ CrossPushNotification.PushNotificationListener.OnUnregistered(Device.Android);
+ StoreRegistrationId(global::Android.App.Application.Context, string.Empty);
+
+ }
+ catch(IOException ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"{Tag} - Error : {ex.Message}");
+ CrossPushNotification.PushNotificationListener.OnError(
+ $"{Tag} - Unregister - {ex.Message}", Device.Android);
+ }
+ });
+ }
+
+ private string GetRegistrationId()
+ {
+ var retVal = string.Empty;
+ var context = global::Android.App.Application.Context;
+ var prefs = GetGCMPreferences(context);
+ var registrationId = prefs.GetString(PushNotificationKey.Token, string.Empty);
+ if(string.IsNullOrEmpty(registrationId))
+ {
+ System.Diagnostics.Debug.WriteLine($"{PushNotificationKey.DomainName} - Registration not found.");
+ return retVal;
+ }
+
+ // Check if app was updated; if so, it must clear the registration ID
+ // since the existing registration ID is not guaranteed to work with
+ // the new app version.
+ var registeredVersion = prefs.GetInt(PushNotificationKey.AppVersion, Java.Lang.Integer.MinValue);
+ var currentVersion = GetAppVersion(context);
+ if(registeredVersion != currentVersion)
+ {
+ System.Diagnostics.Debug.WriteLine($"{PushNotificationKey.DomainName} - App version changed.");
+ return retVal;
+ }
+
+ retVal = registrationId;
+ return retVal;
+ }
+
+ internal static ISharedPreferences GetGCMPreferences(Context context)
+ {
+ // This sample app persists the registration ID in shared preferences, but
+ // how you store the registration ID in your app is up to you.
+ return context.GetSharedPreferences(GcmPreferencesKey, FileCreationMode.Private);
+ }
+
+ internal static int GetAppVersion(Context context)
+ {
+ try
+ {
+ var packageInfo = context.PackageManager.GetPackageInfo(context.PackageName, 0);
+ return packageInfo.VersionCode;
+ }
+ catch(global::Android.Content.PM.PackageManager.NameNotFoundException e)
+ {
+ // should never happen
+ throw new Java.Lang.RuntimeException("Could not get package name: " + e);
+ }
+ }
+
+ internal static void StoreRegistrationId(Context context, string regId)
+ {
+ var prefs = GetGCMPreferences(context);
+ var appVersion = GetAppVersion(context);
+
+ System.Diagnostics.Debug.WriteLine(
+ $"{PushNotificationKey.DomainName} - Saving token on app version {appVersion}");
+
+ var editor = prefs.Edit();
+ editor.PutString(PushNotificationKey.Token, regId);
+ editor.PutInt(PushNotificationKey.AppVersion, appVersion);
+ editor.Commit();
+ }
+ }
+
+ [Service(Exported = false)]
+ public class PushNotificationRegistrationIntentService : IntentService
+ {
+ private const string Tag = "PushNotificationRegistationIntentService";
+ private string[] _topics = new string[] { "global" };
+ private readonly object _syncLock = new object();
+
+ protected override void OnHandleIntent(Intent intent)
+ {
+ try
+ {
+ var extras = intent.Extras;
+ lock(_syncLock)
+ {
+ var instanceID = InstanceID.GetInstance(global::Android.App.Application.Context);
+ var token = instanceID.GetToken(CrossPushNotification.SenderId,
+ GoogleCloudMessaging.InstanceIdScope, null);
+ CrossPushNotification.PushNotificationListener.OnRegistered(token, Device.Android);
+ PushNotificationImplementation.StoreRegistrationId(global::Android.App.Application.Context, token);
+ SubscribeTopics(token);
+ System.Diagnostics.Debug.WriteLine($"{token} - Device registered, registration ID={Tag}");
+ }
+
+ }
+ catch(Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"{ex.Message} - Error : {Tag}");
+ CrossPushNotification.PushNotificationListener.OnError(
+ $"{ex.ToString()} - Register - {Tag}", Device.Android);
+ }
+
+ }
+
+ private void SubscribeTopics(string token)
+ {
+ var pubSub = GcmPubSub.GetInstance(this);
+ foreach(var topic in _topics)
+ {
+ pubSub.Subscribe(token, "/topics/" + topic, null);
+ }
+ }
+ }
+
+ [Service(Exported = false)]
+ [IntentFilter(new string[] { "com.google.android.gms.iid.InstanceID" })]
+ public class PushNotificationInstanceIDListenerService : InstanceIDListenerService
+ {
+ private const string Tag = "PushNotificationInstanceIDLS";
+
+ public override void OnTokenRefresh()
+ {
+ base.OnTokenRefresh();
+ ThreadPool.QueueUserWorkItem(state =>
+ {
+ try
+ {
+ var intent = new Intent(global::Android.App.Application.Context,
+ typeof(PushNotificationRegistrationIntentService));
+ global::Android.App.Application.Context.StartService(intent);
+ }
+ catch(Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"{ex.Message} - Error : {Tag}");
+ CrossPushNotification.PushNotificationListener.OnError(
+ $"{ex.ToString()} - Register - {Tag}", Device.Android);
+ }
+ });
+ }
+ }
+
+ [Service(Exported = false, Name = "pushnotification.plugin.PushNotificationGcmListener")]
+ [IntentFilter(new string[] { "com.google.android.c2dm.intent.RECEIVE" },
+ Categories = new string[] { "com.x8bit.bitwarden" })]
+ public class PushNotificationGcmListener : GcmListenerService
+ {
+ public override void OnMessageReceived(string from, Bundle extras)
+ {
+ if(extras != null && !extras.IsEmpty)
+ {
+ System.Diagnostics.Debug.WriteLine(
+ $"{PushNotificationKey.DomainName} - GCM Listener - Push Received");
+
+ var parameters = new Dictionary();
+ var values = new JObject();
+ foreach(var key in extras.KeySet())
+ {
+ var value = extras.Get(key).ToString();
+
+ if(ValidateJSON(value))
+ {
+ values.Add(key, JObject.Parse(value));
+ }
+ else
+ {
+ values.Add(key, value);
+ }
+
+ parameters.Add(key, extras.Get(key));
+ System.Diagnostics.Debug.WriteLine(
+ $"{PushNotificationKey.DomainName} - GCM Listener - Push Params {key} : {extras.Get(key)}");
+ }
+
+ var context = global::Android.App.Application.Context;
+ CrossPushNotification.PushNotificationListener.OnMessage(values, Device.Android);
+
+ try
+ {
+ var notifyId = 0;
+ var title = context.ApplicationInfo.LoadLabel(context.PackageManager);
+ var message = string.Empty;
+ var tag = string.Empty;
+
+ if(!string.IsNullOrEmpty(CrossPushNotification.NotificationContentTextKey) &&
+ parameters.ContainsKey(CrossPushNotification.NotificationContentTextKey))
+ {
+ message = parameters[CrossPushNotification.NotificationContentTextKey].ToString();
+ }
+ else if(parameters.ContainsKey(PushNotificationKey.Alert))
+ {
+ message = parameters[PushNotificationKey.Alert].ToString();
+ }
+ else if(parameters.ContainsKey(PushNotificationKey.Message))
+ {
+ message = parameters[PushNotificationKey.Message].ToString();
+ }
+ else if(parameters.ContainsKey(PushNotificationKey.Subtitle))
+ {
+ message = parameters[PushNotificationKey.Subtitle].ToString();
+ }
+ else if(parameters.ContainsKey(PushNotificationKey.Text))
+ {
+ message = parameters[PushNotificationKey.Text].ToString();
+ }
+
+ if(!string.IsNullOrEmpty(CrossPushNotification.NotificationContentTitleKey) &&
+ parameters.ContainsKey(CrossPushNotification.NotificationContentTitleKey))
+ {
+ title = parameters[CrossPushNotification.NotificationContentTitleKey].ToString();
+ }
+ else if(parameters.ContainsKey(PushNotificationKey.Title))
+ {
+ if(!string.IsNullOrEmpty(message))
+ {
+ title = parameters[PushNotificationKey.Title].ToString();
+ }
+ else
+ {
+ message = parameters[PushNotificationKey.Title].ToString();
+ }
+ }
+
+ if(string.IsNullOrEmpty(message))
+ {
+ var data = (
+ !string.IsNullOrEmpty(CrossPushNotification.NotificationContentDataKey) &&
+ values[CrossPushNotification.NotificationContentDataKey] != null) ?
+ values[CrossPushNotification.NotificationContentDataKey] :
+ values[PushNotificationKey.Data];
+
+ if(data != null)
+ {
+ if(!string.IsNullOrEmpty(CrossPushNotification.NotificationContentTextKey) &&
+ data[CrossPushNotification.NotificationContentTextKey] != null)
+ {
+ message = data[CrossPushNotification.NotificationContentTextKey].ToString();
+ }
+ else if(data[PushNotificationKey.Alert] != null)
+ {
+ message = data[PushNotificationKey.Alert].ToString();
+ }
+ else if(data[PushNotificationKey.Message] != null)
+ {
+ message = data[PushNotificationKey.Message].ToString();
+ }
+ else if(data[PushNotificationKey.Subtitle] != null)
+ {
+ message = data[PushNotificationKey.Subtitle].ToString();
+ }
+ else if(data[PushNotificationKey.Text] != null)
+ {
+ message = data[PushNotificationKey.Text].ToString();
+ }
+
+ if(!string.IsNullOrEmpty(CrossPushNotification.NotificationContentTitleKey) &&
+ data[CrossPushNotification.NotificationContentTitleKey] != null)
+ {
+ title = data[CrossPushNotification.NotificationContentTitleKey].ToString();
+ }
+ else if(data[PushNotificationKey.Title] != null)
+ {
+ if(!string.IsNullOrEmpty(message))
+ {
+ title = data[PushNotificationKey.Title].ToString();
+ }
+ else
+ {
+ message = data[PushNotificationKey.Title].ToString();
+ }
+ }
+ }
+ }
+
+ if(parameters.ContainsKey(PushNotificationKey.Id))
+ {
+ var str = parameters[PushNotificationKey.Id].ToString();
+ try
+ {
+ notifyId = Convert.ToInt32(str);
+ }
+ catch(Exception)
+ {
+ // Keep the default value of zero for the notify_id, but log the conversion problem.
+ System.Diagnostics.Debug.WriteLine("Failed to convert {0} to an integer", str);
+ }
+ }
+
+ if(parameters.ContainsKey(PushNotificationKey.Tag))
+ {
+ tag = parameters[PushNotificationKey.Tag].ToString();
+ }
+
+ if(!parameters.ContainsKey(PushNotificationKey.Silent) ||
+ !System.Boolean.Parse(parameters[PushNotificationKey.Silent].ToString()))
+ {
+ if(CrossPushNotification.PushNotificationListener.ShouldShowNotification())
+ {
+ CreateNotification(title, message, notifyId, tag, extras);
+ }
+ }
+
+ }
+ catch(Java.Lang.Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine(ex.ToString());
+ }
+ catch(Exception ex1)
+ {
+ System.Diagnostics.Debug.WriteLine(ex1.ToString());
+ }
+ }
+ }
+
+ private void CreateNotification(string title, string message, int notifyId, string tag, Bundle extras)
+ {
+ System.Diagnostics.Debug.WriteLine(
+ $"{PushNotificationKey.DomainName} - PushNotification - Message {title} : {message}");
+
+ NotificationCompat.Builder builder = null;
+ var context = global::Android.App.Application.Context;
+
+ if(CrossPushNotification.SoundUri == null)
+ {
+ CrossPushNotification.SoundUri = RingtoneManager.GetDefaultUri(RingtoneType.Notification);
+ }
+
+ try
+ {
+ if(CrossPushNotification.IconResource == 0)
+ {
+ CrossPushNotification.IconResource = context.ApplicationInfo.Icon;
+ }
+ else
+ {
+ var name = context.Resources.GetResourceName(CrossPushNotification.IconResource);
+ if(name == null)
+ {
+ CrossPushNotification.IconResource = context.ApplicationInfo.Icon;
+ }
+ }
+ }
+ catch(global::Android.Content.Res.Resources.NotFoundException ex)
+ {
+ CrossPushNotification.IconResource = context.ApplicationInfo.Icon;
+ System.Diagnostics.Debug.WriteLine(ex.ToString());
+ }
+
+ var resultIntent = context.PackageManager.GetLaunchIntentForPackage(context.PackageName);
+ if(extras != null)
+ {
+ resultIntent.PutExtras(extras);
+ }
+
+ // Create a PendingIntent; we're only using one PendingIntent (ID = 0):
+ const int pendingIntentId = 0;
+ var resultPendingIntent = PendingIntent.GetActivity(context, pendingIntentId,
+ resultIntent, PendingIntentFlags.OneShot);
+
+ // Build the notification
+ builder = new NotificationCompat.Builder(context)
+ .SetAutoCancel(true) // dismiss the notification from the notification area when the user clicks on it
+ .SetContentIntent(resultPendingIntent) // start up this activity when the user clicks the intent.
+ .SetContentTitle(title) // Set the title
+ .SetSound(CrossPushNotification.SoundUri)
+ .SetSmallIcon(CrossPushNotification.IconResource) // This is the icon to display
+ .SetContentText(message); // the message to display.
+
+ if(Build.VERSION.SdkInt >= BuildVersionCodes.JellyBean)
+ {
+ // Using BigText notification style to support long message
+ var style = new NotificationCompat.BigTextStyle();
+ style.BigText(message);
+ builder.SetStyle(style);
+ }
+
+ var notificationManager = (NotificationManager)context.GetSystemService(NotificationService);
+ notificationManager.Notify(tag, notifyId, builder.Build());
+ }
+
+ private static bool ValidateJSON(string s)
+ {
+ try
+ {
+ JObject.Parse(s);
+ return true;
+ }
+ catch(JsonReaderException ex)
+ {
+ System.Diagnostics.Debug.WriteLine(ex.ToString());
+ return false;
+ }
+ }
+ }
+
+ [BroadcastReceiver(Exported = true, Permission = "com.google.android.c2dm.permission.SEND")]
+ [IntentFilter(new string[] { "com.google.android.c2dm.intent.RECEIVE" },
+ Categories = new string[] { "com.x8bit.bitwarden" })]
+ public class PushNotificationsReceiver : GcmReceiver
+ { }
+
+ [Service]
+ public class PushNotificationService : Service
+ {
+ public override void OnCreate()
+ {
+ base.OnCreate();
+ System.Diagnostics.Debug.WriteLine("Push Notification Service - Created");
+ }
+
+ public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
+ {
+ System.Diagnostics.Debug.WriteLine("Push Notification Service - Started");
+ return StartCommandResult.Sticky;
+ }
+
+ public override IBinder OnBind(Intent intent)
+ {
+ System.Diagnostics.Debug.WriteLine("Push Notification Service - Binded");
+ return null;
+ }
+
+ public override void OnDestroy()
+ {
+ System.Diagnostics.Debug.WriteLine("Push Notification Service - Destroyed");
+ base.OnDestroy();
+ }
+ }
+
+ internal class CrossPushNotification
+ {
+ private static Lazy Implementation = new Lazy(
+ () => new PushNotificationImplementation(),
+ LazyThreadSafetyMode.PublicationOnly);
+ public static bool IsInitialized => PushNotificationListener != null;
+ public static IPushNotificationListener PushNotificationListener { get; private set; }
+
+ public static string SenderId { get; set; }
+ public static string NotificationContentTitleKey { get; set; }
+ public static string NotificationContentTextKey { get; set; }
+ public static string NotificationContentDataKey { get; set; }
+ public static int IconResource { get; set; }
+ public static global::Android.Net.Uri SoundUri { get; set; }
+
+ public static void Initialize(T listener, string senderId) where T : IPushNotificationListener
+ {
+ SenderId = senderId;
+
+ if(PushNotificationListener == null)
+ {
+ PushNotificationListener = listener;
+ System.Diagnostics.Debug.WriteLine("PushNotification plugin initialized.");
+ }
+ else
+ {
+ System.Diagnostics.Debug.WriteLine("PushNotification plugin already initialized.");
+ }
+ }
+
+ public static void Initialize(string senderId) where T : IPushNotificationListener, new()
+ {
+ Initialize(new T(), senderId);
+ }
+
+ public static IPushNotification Current
+ {
+ get
+ {
+ if(!IsInitialized)
+ {
+ throw new Exception("Not initialized.");
+ }
+
+ var ret = Implementation.Value;
+ if(ret == null)
+ {
+ throw new Exception("Not in PCL");
+ }
+
+ return ret;
+ }
+ }
+ }
+}
diff --git a/src/Android/packages.config b/src/Android/packages.config
index 3b879d8f7..cb6f0d272 100644
--- a/src/Android/packages.config
+++ b/src/Android/packages.config
@@ -69,7 +69,6 @@
-
diff --git a/src/App/Abstractions/Services/IPushNotification.cs b/src/App/Abstractions/Services/IPushNotification.cs
new file mode 100644
index 000000000..e98aad42b
--- /dev/null
+++ b/src/App/Abstractions/Services/IPushNotification.cs
@@ -0,0 +1,9 @@
+namespace Bit.App.Abstractions
+{
+ public interface IPushNotification
+ {
+ string Token { get; }
+ void Register();
+ void Unregister();
+ }
+}
diff --git a/src/App/Abstractions/Services/IPushNotificationListener.cs b/src/App/Abstractions/Services/IPushNotificationListener.cs
new file mode 100644
index 000000000..0570dc71c
--- /dev/null
+++ b/src/App/Abstractions/Services/IPushNotificationListener.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json.Linq;
+
+namespace Bit.App.Abstractions
+{
+ public interface IPushNotificationListener
+ {
+ void OnMessage(JObject values, string device);
+ void OnRegistered(string token, string device);
+ void OnUnregistered(string device);
+ void OnError(string message, string device);
+ bool ShouldShowNotification();
+ }
+}
diff --git a/src/App/App.csproj b/src/App/App.csproj
index d939258dc..776bdec3c 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -44,6 +44,8 @@
+
+
@@ -87,6 +89,7 @@
+
@@ -355,6 +358,7 @@
+
@@ -528,14 +532,6 @@
..\..\packages\Xam.Plugins.Settings.3.0.1\lib\netstandard1.0\Plugin.Settings.Abstractions.dll
-
- ..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\PushNotification.Plugin.dll
- True
-
-
- ..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\PushNotification.Plugin.Abstractions.dll
- True
-
..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll
True
diff --git a/src/App/Enums/DeviceType.cs b/src/App/Enums/DeviceType.cs
new file mode 100644
index 000000000..f1e28dd68
--- /dev/null
+++ b/src/App/Enums/DeviceType.cs
@@ -0,0 +1,9 @@
+namespace Bit.App.Enums
+{
+ public enum DeviceType
+ {
+ Android = 0,
+ iOS = 1,
+ UWP = 16
+ }
+}
diff --git a/src/App/Models/Api/Request/DeviceRequest.cs b/src/App/Models/Api/Request/DeviceRequest.cs
index 1ae298cbb..3b8e9a968 100644
--- a/src/App/Models/Api/Request/DeviceRequest.cs
+++ b/src/App/Models/Api/Request/DeviceRequest.cs
@@ -1,5 +1,5 @@
using Bit.App.Abstractions;
-using PushNotification.Plugin.Abstractions;
+using Bit.App.Enums;
using Xamarin.Forms;
namespace Bit.App.Models.Api
diff --git a/src/App/Models/Api/Response/DeviceResponse.cs b/src/App/Models/Api/Response/DeviceResponse.cs
index 9cb1483a3..eec3ceb23 100644
--- a/src/App/Models/Api/Response/DeviceResponse.cs
+++ b/src/App/Models/Api/Response/DeviceResponse.cs
@@ -1,5 +1,5 @@
-using System;
-using PushNotification.Plugin.Abstractions;
+using Bit.App.Enums;
+using System;
namespace Bit.App.Models.Api
{
diff --git a/src/App/Pages/LoginPage.cs b/src/App/Pages/LoginPage.cs
index 4c996e047..69cbb8c3a 100644
--- a/src/App/Pages/LoginPage.cs
+++ b/src/App/Pages/LoginPage.cs
@@ -7,7 +7,6 @@ using XLabs.Ioc;
using Acr.UserDialogs;
using System.Threading.Tasks;
using Plugin.Settings.Abstractions;
-using PushNotification.Plugin.Abstractions;
using Bit.App.Utilities;
namespace Bit.App.Pages
diff --git a/src/App/Pages/LoginTwoFactorPage.cs b/src/App/Pages/LoginTwoFactorPage.cs
index a2dbd97e6..dca25d1f0 100644
--- a/src/App/Pages/LoginTwoFactorPage.cs
+++ b/src/App/Pages/LoginTwoFactorPage.cs
@@ -6,7 +6,6 @@ using Xamarin.Forms;
using XLabs.Ioc;
using Acr.UserDialogs;
using System.Threading.Tasks;
-using PushNotification.Plugin.Abstractions;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.App.Enums;
diff --git a/src/App/Pages/Settings/SettingsFeaturesPage.cs b/src/App/Pages/Settings/SettingsFeaturesPage.cs
index ce2ec90f3..254011f96 100644
--- a/src/App/Pages/Settings/SettingsFeaturesPage.cs
+++ b/src/App/Pages/Settings/SettingsFeaturesPage.cs
@@ -4,10 +4,7 @@ using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
using Bit.App.Controls;
-using Acr.UserDialogs;
using Plugin.Settings.Abstractions;
-using Plugin.Fingerprint.Abstractions;
-using PushNotification.Plugin.Abstractions;
namespace Bit.App.Pages
{
diff --git a/src/App/Pages/Settings/SettingsPage.cs b/src/App/Pages/Settings/SettingsPage.cs
index 416342a1d..b5ccc627b 100644
--- a/src/App/Pages/Settings/SettingsPage.cs
+++ b/src/App/Pages/Settings/SettingsPage.cs
@@ -7,7 +7,6 @@ using Bit.App.Controls;
using Acr.UserDialogs;
using Plugin.Settings.Abstractions;
using Plugin.Fingerprint.Abstractions;
-using PushNotification.Plugin.Abstractions;
using Bit.App.Utilities;
namespace Bit.App.Pages
diff --git a/src/App/Pages/Vault/VaultListLoginsPage.cs b/src/App/Pages/Vault/VaultListLoginsPage.cs
index 2114cec49..0cbac5a96 100644
--- a/src/App/Pages/Vault/VaultListLoginsPage.cs
+++ b/src/App/Pages/Vault/VaultListLoginsPage.cs
@@ -9,7 +9,6 @@ using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
using Bit.App.Utilities;
-using PushNotification.Plugin.Abstractions;
using Plugin.Settings.Abstractions;
using Plugin.Connectivity.Abstractions;
using System.Collections.Generic;
diff --git a/src/App/Services/PushNotificationListener.cs b/src/App/Services/PushNotificationListener.cs
index c59109b81..07eda012e 100644
--- a/src/App/Services/PushNotificationListener.cs
+++ b/src/App/Services/PushNotificationListener.cs
@@ -1,6 +1,4 @@
-using PushNotification.Plugin.Abstractions;
using System.Diagnostics;
-using PushNotification.Plugin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Bit.App.Abstractions;
@@ -37,7 +35,7 @@ namespace Bit.App.Services
_resolved = true;
}
- public void OnMessage(JObject value, DeviceType deviceType)
+ public void OnMessage(JObject value, string deviceType)
{
Resolve();
@@ -144,7 +142,7 @@ namespace Bit.App.Services
}
}
- public async void OnRegistered(string token, DeviceType deviceType)
+ public async void OnRegistered(string token, string deviceType)
{
Resolve();
@@ -168,12 +166,12 @@ namespace Bit.App.Services
}
}
- public void OnUnregistered(DeviceType deviceType)
+ public void OnUnregistered(string deviceType)
{
Debug.WriteLine("Push Notification - Device Unnregistered");
}
- public void OnError(string message, DeviceType deviceType)
+ public void OnError(string message, string deviceType)
{
Debug.WriteLine(string.Format("Push notification error - {0}", message));
}
diff --git a/src/App/Utilities/PushNotificationKey.cs b/src/App/Utilities/PushNotificationKey.cs
new file mode 100644
index 000000000..3665ace10
--- /dev/null
+++ b/src/App/Utilities/PushNotificationKey.cs
@@ -0,0 +1,78 @@
+namespace Bit.App.Utilities
+{
+ public static class PushNotificationKey
+ {
+ ///
+ /// Type
+ ///
+ public const string Type = "type";
+ ///
+ /// Error
+ ///
+ public const string Error = "error";
+ ///
+ /// Domain Name
+ ///
+ public const string DomainName = "CrossPushNotification";
+ ///
+ /// Title
+ ///
+ public const string Title = "title";
+ ///
+ /// Text
+ ///
+ public const string Text = "text";
+ ///
+ /// Subtitle
+ ///
+ public const string Subtitle = "subtitle";
+ ///
+ /// Message
+ ///
+ public const string Message = "message";
+ ///
+ /// Silent
+ ///
+ public const string Silent = "silent";
+ ///
+ /// Alert
+ ///
+ public const string Alert = "alert";
+ ///
+ /// Data
+ ///
+ public const string Data = "data";
+ ///
+ /// Token
+ ///
+ public const string Token = "token";
+ ///
+ /// App Version
+ ///
+ public const string AppVersion = "appVersion";
+ ///
+ /// IntentFromGcmMessage
+ ///
+ public const string IntentFromGcmMessage = "com.google.android.c2dm.intent.RECEIVE";
+ ///
+ /// BackOffMilliseconds
+ ///
+ public const string BackOffMilliseconds = "backoff_ms";
+ ///
+ /// ErrorServiceNotAvailable
+ ///
+ public const string ErrorServiceNotAvailable = "SERVICE_NOT_AVAILABLE";
+ ///
+ /// Tag
+ ///
+ public const string Tag = "tag";
+ ///
+ /// Id
+ ///
+ public const string Id = "id";
+ ///
+ /// RegistrationComplete
+ ///
+ public const string RegistrationComplete = "registrationComplete";
+ }
+}
diff --git a/src/App/packages.config b/src/App/packages.config
index c026688e3..816268646 100644
--- a/src/App/packages.config
+++ b/src/App/packages.config
@@ -16,7 +16,6 @@
-
diff --git a/src/UWP/UWP.csproj b/src/UWP/UWP.csproj
index 29b577e5e..3b0678f48 100644
--- a/src/UWP/UWP.csproj
+++ b/src/UWP/UWP.csproj
@@ -170,9 +170,6 @@
3.0.2
-
- 1.2.4
-
3.0.1
diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs
index b2a002f91..266ec429a 100644
--- a/src/iOS/AppDelegate.cs
+++ b/src/iOS/AppDelegate.cs
@@ -1,8 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using XLabs.Ioc;
-
using Foundation;
using UIKit;
using Bit.App.Abstractions;
@@ -16,7 +13,6 @@ using Plugin.Settings.Abstractions;
using System.Diagnostics;
using Xamarin.Forms;
using Bit.iOS.Core.Services;
-using PushNotification.Plugin;
using Plugin.Connectivity.Abstractions;
using Bit.App.Pages;
using HockeyApp.iOS;
@@ -81,14 +77,16 @@ namespace Bit.iOS
UINavigationBar.Appearance.ShadowImage = new UIImage();
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
UIBarButtonItem.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).TintColor = primaryColor;
- UIButton.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).SetTitleColor(primaryColor, UIControlState.Normal);
+ UIButton.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).SetTitleColor(primaryColor,
+ UIControlState.Normal);
UIButton.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).TintColor = primaryColor;
UIStepper.Appearance.TintColor = grayLight;
UISlider.Appearance.TintColor = primaryColor;
- MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, "ShowAppExtension", (sender, page) =>
+ MessagingCenter.Subscribe(
+ Xamarin.Forms.Application.Current, "ShowAppExtension", (sender, page) =>
{
- var itemProvider = new NSItemProvider(new NSDictionary(), iOS.Core.Constants.UTTypeAppExtensionSetup);
+ var itemProvider = new NSItemProvider(new NSDictionary(), Core.Constants.UTTypeAppExtensionSetup);
var extensionItem = new NSExtensionItem();
extensionItem.Attachments = new NSItemProvider[] { itemProvider };
var activityViewController = new UIActivityViewController(new NSExtensionItem[] { extensionItem }, null);
@@ -112,7 +110,8 @@ namespace Bit.iOS
UIApplication.SharedApplication.StatusBarHidden = false;
UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent;
- MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, "ShowStatusBar", (sender, show) =>
+ MessagingCenter.Subscribe(Xamarin.Forms.Application.Current,
+ "ShowStatusBar", (sender, show) =>
{
UIApplication.SharedApplication.SetStatusBarHidden(!show, false);
});
@@ -196,7 +195,8 @@ namespace Bit.iOS
Debug.WriteLine("WillEnterForeground");
}
- public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
+ public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication,
+ NSObject annotation)
{
return true;
}
@@ -217,12 +217,14 @@ namespace Bit.iOS
}
}
- public override void DidRegisterUserNotificationSettings(UIApplication application, UIUserNotificationSettings notificationSettings)
+ public override void DidRegisterUserNotificationSettings(UIApplication application,
+ UIUserNotificationSettings notificationSettings)
{
application.RegisterForRemoteNotifications();
}
- public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action completionHandler)
+ public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
+ Action completionHandler)
{
if(CrossPushNotification.Current is IPushNotificationHandler)
{
diff --git a/src/iOS/Services/PushNotificationImplementation.cs b/src/iOS/Services/PushNotificationImplementation.cs
new file mode 100644
index 000000000..8f83164d7
--- /dev/null
+++ b/src/iOS/Services/PushNotificationImplementation.cs
@@ -0,0 +1,152 @@
+using Bit.App.Abstractions;
+using Bit.App.Utilities;
+using Foundation;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+using UIKit;
+using Xamarin.Forms;
+
+namespace Bit.iOS.Services
+{
+ public class PushNotificationImplementation : IPushNotification, IPushNotificationHandler
+ {
+ public string Token => NSUserDefaults.StandardUserDefaults.StringForKey(PushNotificationKey.Token);
+
+ public void Register()
+ {
+ var userNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge |
+ UIUserNotificationType.Sound;
+ var settings = UIUserNotificationSettings.GetSettingsForTypes(userNotificationTypes, null);
+ UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
+ }
+
+ public void Unregister()
+ {
+ UIApplication.SharedApplication.UnregisterForRemoteNotifications();
+ OnUnregisteredSuccess();
+ }
+
+ private static string DictionaryToJson(NSDictionary dictionary)
+ {
+ NSError error;
+ var json = NSJsonSerialization.Serialize(dictionary, NSJsonWritingOptions.PrettyPrinted, out error);
+ return json.ToString(NSStringEncoding.UTF8);
+ }
+
+ public void OnMessageReceived(NSDictionary userInfo)
+ {
+ var parameters = new Dictionary();
+ var json = DictionaryToJson(userInfo);
+ var values = JObject.Parse(json);
+
+ var keyAps = new NSString("aps");
+ if(userInfo.ContainsKey(keyAps))
+ {
+ var aps = userInfo.ValueForKey(keyAps) as NSDictionary;
+ if(aps != null)
+ {
+ foreach(var apsKey in aps)
+ {
+ parameters.Add(apsKey.Key.ToString(), apsKey.Value);
+ JToken temp;
+ if(!values.TryGetValue(apsKey.Key.ToString(), out temp))
+ {
+ values.Add(apsKey.Key.ToString(), apsKey.Value.ToString());
+ }
+ }
+ }
+ }
+
+ CrossPushNotification.PushNotificationListener.OnMessage(values, Device.iOS);
+ }
+
+ public void OnErrorReceived(NSError error)
+ {
+ Debug.WriteLine("{0} - Registration Failed.", PushNotificationKey.DomainName);
+ CrossPushNotification.PushNotificationListener.OnError(error.LocalizedDescription, Device.iOS);
+ }
+
+ public void OnRegisteredSuccess(NSData token)
+ {
+ Debug.WriteLine("{0} - Succesfully Registered.", PushNotificationKey.DomainName);
+
+ var trimmedDeviceToken = token.Description;
+ if(!string.IsNullOrWhiteSpace(trimmedDeviceToken))
+ {
+ trimmedDeviceToken = trimmedDeviceToken.Trim('<');
+ trimmedDeviceToken = trimmedDeviceToken.Trim('>');
+ trimmedDeviceToken = trimmedDeviceToken.Trim();
+ trimmedDeviceToken = trimmedDeviceToken.Replace(" ", "");
+ }
+
+ Console.WriteLine("{0} - Token: {1}", PushNotificationKey.DomainName, trimmedDeviceToken);
+ CrossPushNotification.PushNotificationListener.OnRegistered(trimmedDeviceToken, Device.iOS);
+ NSUserDefaults.StandardUserDefaults.SetString(trimmedDeviceToken, PushNotificationKey.Token);
+ NSUserDefaults.StandardUserDefaults.Synchronize();
+ }
+
+ public void OnUnregisteredSuccess()
+ {
+ CrossPushNotification.PushNotificationListener.OnUnregistered(Device.iOS);
+ NSUserDefaults.StandardUserDefaults.SetString(string.Empty, PushNotificationKey.Token);
+ NSUserDefaults.StandardUserDefaults.Synchronize();
+ }
+ }
+
+ public interface IPushNotificationHandler
+ {
+ void OnMessageReceived(NSDictionary parameters);
+ void OnErrorReceived(NSError error);
+ void OnRegisteredSuccess(NSData token);
+ void OnUnregisteredSuccess();
+ }
+
+ internal class CrossPushNotification
+ {
+ private static Lazy Implementation = new Lazy(
+ () => new PushNotificationImplementation(),
+ LazyThreadSafetyMode.PublicationOnly);
+ public static bool IsInitialized => PushNotificationListener != null;
+ public static IPushNotificationListener PushNotificationListener { get; private set; }
+
+ public static void Initialize(T listener) where T : IPushNotificationListener
+ {
+ if(PushNotificationListener == null)
+ {
+ PushNotificationListener = listener;
+ Debug.WriteLine("PushNotification plugin initialized.");
+ }
+ else
+ {
+ Debug.WriteLine("PushNotification plugin already initialized.");
+ }
+ }
+
+ public static void Initialize() where T : IPushNotificationListener, new()
+ {
+ Initialize(new T());
+ }
+
+ public static IPushNotification Current
+ {
+ get
+ {
+ if(!IsInitialized)
+ {
+ throw new Exception("Not initialized.");
+ }
+
+ var ret = Implementation.Value;
+ if(ret == null)
+ {
+ throw new Exception("Not in PCL");
+ }
+
+ return ret;
+ }
+ }
+ }
+}
diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj
index e6d460c00..6a69806eb 100644
--- a/src/iOS/iOS.csproj
+++ b/src/iOS/iOS.csproj
@@ -238,6 +238,7 @@
+
Designer
@@ -324,14 +325,6 @@
..\..\packages\Xam.Plugins.Settings.3.0.1\lib\Xamarin.iOS10\Plugin.Settings.Abstractions.dll
-
- ..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\Xamarin.iOS10\PushNotification.Plugin.dll
- True
-
-
- ..\..\packages\Xam.Plugin.PushNotification.1.2.4\lib\Xamarin.iOS10\PushNotification.Plugin.Abstractions.dll
- True
-
..\..\packages\SimpleInjector.4.0.8\lib\netstandard1.3\SimpleInjector.dll
diff --git a/src/iOS/packages.config b/src/iOS/packages.config
index 27e476071..53d57503a 100644
--- a/src/iOS/packages.config
+++ b/src/iOS/packages.config
@@ -67,7 +67,6 @@
-