From 7c6cc7b246f14ba3c402417e1cd59777dca1df9b Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 9 Oct 2017 23:45:23 -0400 Subject: [PATCH] local push notification implementation from lib --- src/Android/Android.csproj | 9 +- src/Android/MainApplication.cs | 2 - .../PushNotificationImplementation.cs | 564 ++++++++++++++++++ src/Android/packages.config | 1 - .../Services/IPushNotification.cs | 9 + .../Services/IPushNotificationListener.cs | 13 + src/App/App.csproj | 12 +- src/App/Enums/DeviceType.cs | 9 + src/App/Models/Api/Request/DeviceRequest.cs | 2 +- src/App/Models/Api/Response/DeviceResponse.cs | 4 +- src/App/Pages/LoginPage.cs | 1 - src/App/Pages/LoginTwoFactorPage.cs | 1 - .../Pages/Settings/SettingsFeaturesPage.cs | 3 - src/App/Pages/Settings/SettingsPage.cs | 1 - src/App/Pages/Vault/VaultListLoginsPage.cs | 1 - src/App/Services/PushNotificationListener.cs | 10 +- src/App/Utilities/PushNotificationKey.cs | 78 +++ src/App/packages.config | 1 - src/UWP/UWP.csproj | 3 - src/iOS/AppDelegate.cs | 24 +- .../PushNotificationImplementation.cs | 152 +++++ src/iOS/iOS.csproj | 9 +- src/iOS/packages.config | 1 - 23 files changed, 851 insertions(+), 59 deletions(-) create mode 100644 src/Android/Services/PushNotificationImplementation.cs create mode 100644 src/App/Abstractions/Services/IPushNotification.cs create mode 100644 src/App/Abstractions/Services/IPushNotificationListener.cs create mode 100644 src/App/Enums/DeviceType.cs create mode 100644 src/App/Utilities/PushNotificationKey.cs create mode 100644 src/iOS/Services/PushNotificationImplementation.cs 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 @@ -