From 90b53e3cd49fb6e9df0e8a0d3f4a2cfc10fc157c Mon Sep 17 00:00:00 2001 From: ichan-mb Date: Sat, 29 Jul 2023 06:23:51 +0700 Subject: [PATCH] Push Notification: fix compatibility with latest Android --- .../AndroidPermissionsInternal.uno | 5 + .../AndroidPermissionsInternal.uxl | 4 + .../Fuse.Android.Permissions/Permissions.uno | 1 + .../Fuse.Android.Permissions/Permissions.uxl | 4 + .../Fuse.PushNotifications/Android/Impl.uno | 82 +++++++-- .../Android/PushNotificationReceiver.java | 170 +++++++++--------- Source/Fuse.PushNotifications/Common.uno | 10 +- Source/Fuse.PushNotifications/Docs/Guide.md | 5 + .../Fuse.PushNotifications.unoproj | 3 +- 9 files changed, 181 insertions(+), 103 deletions(-) diff --git a/Source/Fuse.Android.Permissions/AndroidPermissionsInternal.uno b/Source/Fuse.Android.Permissions/AndroidPermissionsInternal.uno index 0aabc65f2..80127222d 100644 --- a/Source/Fuse.Android.Permissions/AndroidPermissionsInternal.uno +++ b/Source/Fuse.Android.Permissions/AndroidPermissionsInternal.uno @@ -769,5 +769,10 @@ namespace Fuse.Android.Permissions.Internal { return new PlatformPermission("android.permission.WRITE_VOICEMAIL"); } + [TargetSpecificImplementation] + internal static extern PlatformPermission _post_notification() + { + return new PlatformPermission("android.permission.POST_NOTIFICATIONS"); + } } } diff --git a/Source/Fuse.Android.Permissions/AndroidPermissionsInternal.uxl b/Source/Fuse.Android.Permissions/AndroidPermissionsInternal.uxl index 09eaf6cb5..d78d5003f 100644 --- a/Source/Fuse.Android.Permissions/AndroidPermissionsInternal.uxl +++ b/Source/Fuse.Android.Permissions/AndroidPermissionsInternal.uxl @@ -611,5 +611,9 @@ + + + + diff --git a/Source/Fuse.Android.Permissions/Permissions.uno b/Source/Fuse.Android.Permissions/Permissions.uno index c874ee5ab..df9409809 100644 --- a/Source/Fuse.Android.Permissions/Permissions.uno +++ b/Source/Fuse.Android.Permissions/Permissions.uno @@ -271,6 +271,7 @@ namespace Fuse.Android.Permissions public static PlatformPermission WRITE_SYNC_SETTINGS { get { return Internal.Android._write_sync_settings(); } } public static PlatformPermission WRITE_USER_DICTIONARY { get { return Internal.Android._write_user_dictionary(); } } public static PlatformPermission WRITE_VOICEMAIL { get { return Internal.Android._write_voicemail(); } } + public static PlatformPermission POST_NOTIFICATIONS { get { return Internal.Android._post_notification(); } } } } diff --git a/Source/Fuse.Android.Permissions/Permissions.uxl b/Source/Fuse.Android.Permissions/Permissions.uxl index 12c77e1e8..9ffa31bbe 100644 --- a/Source/Fuse.Android.Permissions/Permissions.uxl +++ b/Source/Fuse.Android.Permissions/Permissions.uxl @@ -611,5 +611,9 @@ + + + + diff --git a/Source/Fuse.PushNotifications/Android/Impl.uno b/Source/Fuse.PushNotifications/Android/Impl.uno index 2b5ba0218..aff549626 100644 --- a/Source/Fuse.PushNotifications/Android/Impl.uno +++ b/Source/Fuse.PushNotifications/Android/Impl.uno @@ -5,6 +5,7 @@ using Fuse; using Fuse.Controls; using Fuse.Triggers; using Fuse.Resources; +using Fuse.Android.Permissions; using Fuse.Platform; using Uno.Compiler.ExportTargetInterop; @@ -16,6 +17,7 @@ namespace Fuse.PushNotifications "android.app.Activity", "android.os.AsyncTask", "android.app.Notification", + "android.app.PendingIntent", "android.content.Context", "android.content.Intent", "android.media.RingtoneManager", @@ -33,7 +35,7 @@ namespace Fuse.PushNotifications "com.google.android.gms.common.ConnectionResult", "com.google.android.gms.common.GoogleApiAvailability" )] - [Require("Gradle.Dependency.Implementation", "com.google.firebase:firebase-messaging:20.0.1")] + [Require("Gradle.Dependency.Implementation", "com.google.firebase:firebase-messaging:23.2.0")] extern(Android) internal class AndroidImpl { @@ -71,20 +73,30 @@ namespace Fuse.PushNotifications // Set up FCM final Activity activity = com.fuse.Activity.getRootActivity(); activity.runOnUiThread(new Runnable() { - @Override - public void run() { - // Check device for Play Services APK. If check succeeds, proceed with FCM registration. - if (@{CheckPlayServices():Call()}) { - - String _regID = com.google.firebase.iid.FirebaseInstanceId.getInstance().getToken(); - if (_regID != null) { - @{getRegistrationIdSuccess(string):Call(_regID)}; + @Override + public void run() { + // Check device for Play Services APK. If check succeeds, proceed with FCM registration. + if (@{CheckPlayServices():Call()}) { + com.google.firebase.messaging.FirebaseMessaging.getInstance().getToken().addOnCompleteListener(new com.google.android.gms.tasks.OnCompleteListener() { + @Override + public void onComplete(com.google.android.gms.tasks.Task task) { + if (!task.isSuccessful()) { + @{getRegistrationIdError(string):Call(task.getException().toString())}; + return; + } + // Get new FCM registration token + String token = task.getResult(); + @{getRegistrationIdSuccess(string):Call(token)}; } - } else { - @{getRegistrationIdError(string):Call("Google Play Services need to be updated")}; - } + }); + } else { + @{getRegistrationIdError(string):Call("Google Play Services need to be updated")}; } - }); + } + }); + #if (!@(Project.Android.PushNotifications.RegisterOnLaunch:IsSet)) || @(Project.Android.PushNotifications.RegisterOnLaunch:Or(0)) + @{RegisterForPushNotifications():Call()}; + #endif @} @@ -110,6 +122,38 @@ namespace Fuse.PushNotifications return true; @} + [Foreign(Language.Java)] + internal static void RegisterForPushNotifications() + @{ + // This is only necessary for API level >= 33 (TIRAMISU) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + @{RequestPermission():Call()}; + } + @} + + static void RequestPermission() + { + Permissions.Request(new PlatformPermission[] { Permissions.Android.POST_NOTIFICATIONS}).Then(OnPermissions, OnRejected); + } + + static void OnPermissions(PlatformPermission[] grantedPermissions) + { + if(grantedPermissions.Length == 1) + { + // Success + return; + } + else + { + getRegistrationIdError("Push Notification is not granted"); + } + } + + static void OnRejected(Exception e) + { + getRegistrationIdError(e.Message); + } + [Foreign(Language.Java), ForeignFixedName] static void RegistrationIDUpdated(string regid) @{ @@ -429,7 +473,17 @@ namespace Fuse.PushNotifications intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.setAction(PushNotificationReceiver.ACTION); intent.replaceExtras(payload); - android.app.PendingIntent pendingIntent = android.app.PendingIntent.getActivity(context, id, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) + { + pendingIntent = android.app.PendingIntent.getActivity + (context, id, intent, android.app.PendingIntent.FLAG_IMMUTABLE | android.app.PendingIntent.FLAG_UPDATE_CURRENT); + } + else + { + pendingIntent = android.app.PendingIntent.getActivity + (context, id, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT); + } android.app.NotificationManager notificationManager = (android.app.NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); /// Setup Default notification channel properties to fallback on diff --git a/Source/Fuse.PushNotifications/Android/PushNotificationReceiver.java b/Source/Fuse.PushNotifications/Android/PushNotificationReceiver.java index 0d3fbd28c..011634005 100644 --- a/Source/Fuse.PushNotifications/Android/PushNotificationReceiver.java +++ b/Source/Fuse.PushNotifications/Android/PushNotificationReceiver.java @@ -52,9 +52,9 @@ public void onMessageReceived(RemoteMessage message) JSONObject jsonObj = new JSONObject(jsonStr); - jsonStr = jsonObj.toString(); + jsonStr = jsonObj.toString(); - bundle = jsonStrToBundle(jsonStr); + bundle = jsonStrToBundle(jsonStr); } catch (JSONException je) { Log.d("onMessageReceived", "BAD JSON"); @@ -69,87 +69,87 @@ public void onMessageReceived(RemoteMessage message) } } - public static Bundle jsonStrToBundle(String jsonStr) { - Bundle bundle = new Bundle(); - - try { - JSONObject jsonObject = new JSONObject(jsonStr.trim()); - bundle = handleJSONObject(jsonObject); - } catch (JSONException notObject) { - try { - JSONArray jsonArr = new JSONArray(jsonStr.trim()); - bundle = handleJSONArray(jsonArr); - } catch (JSONException badJSON) { - Log.d("jsonStrToBundle", "BAD JSON"); - } - } - - return bundle; - } - - - public static Bundle handleJSONArray(JSONArray jsonArray) { - Bundle bundle = new Bundle(); - - for (int i = 0; i < jsonArray.length(); i++) { - - try { - Object jsonArrayValue = jsonArray.get(i); - - if (jsonArrayValue instanceof JSONObject) { - bundle.putBundle("" + i, handleJSONObject((JSONObject) jsonArrayValue)); - } else if (jsonArrayValue instanceof JSONArray) { - bundle.putBundle("" + i, handleJSONArray((JSONArray) jsonArrayValue)); - } else if (jsonArrayValue instanceof String) { - bundle.putString("" + i, "" + jsonArrayValue); - } else if (jsonArrayValue instanceof Boolean) { - bundle.putBoolean("" + i, (boolean) jsonArrayValue); - } else if (jsonArrayValue instanceof Integer) { - bundle.putInt("" + i, (int) jsonArrayValue); - } else if (jsonArrayValue instanceof Double) { - bundle.putDouble("" + i, (double) jsonArrayValue); - } else if (jsonArrayValue instanceof Long) { - bundle.putLong("" + i, (long) jsonArrayValue); - } - } catch (JSONException je) { - Log.d("handleJSONArray", "BAD JSON VALUE IN JSON ARRAY, AT POSITION: " + i); - } - } - - return bundle; - } - - public static Bundle handleJSONObject(JSONObject jsonObject) { - Bundle bundle = new Bundle(); - - Iterator keys = jsonObject.keys(); - - while(keys.hasNext()) { - String keyStr = keys.next(); - - try { - Object keyValue = jsonObject.get(keyStr); - - if (keyValue instanceof JSONObject) { - bundle.putBundle(keyStr, handleJSONObject((JSONObject) keyValue)); - } else if (keyValue instanceof JSONArray) { - bundle.putBundle(keyStr, handleJSONArray((JSONArray) keyValue)); - } else if (keyValue instanceof String) { - bundle.putString(keyStr, "" + keyValue); - } else if (keyValue instanceof Boolean) { - bundle.putBoolean(keyStr, (boolean) keyValue); - } else if (keyValue instanceof Integer) { - bundle.putInt(keyStr, (int) keyValue); - } else if (keyValue instanceof Double) { - bundle.putDouble(keyStr, (double) keyValue); - } else if (keyValue instanceof Long) { - bundle.putLong(keyStr, (long) keyValue); - } - } catch (JSONException je) { - Log.d("handleJSONObject", "BAD JSON VALUE IN JSON OBJECT, AT KEY: " + keyStr); - } - } - - return bundle; - } + public static Bundle jsonStrToBundle(String jsonStr) { + Bundle bundle = new Bundle(); + + try { + JSONObject jsonObject = new JSONObject(jsonStr.trim()); + bundle = handleJSONObject(jsonObject); + } catch (JSONException notObject) { + try { + JSONArray jsonArr = new JSONArray(jsonStr.trim()); + bundle = handleJSONArray(jsonArr); + } catch (JSONException badJSON) { + Log.d("jsonStrToBundle", "BAD JSON"); + } + } + + return bundle; + } + + + public static Bundle handleJSONArray(JSONArray jsonArray) { + Bundle bundle = new Bundle(); + + for (int i = 0; i < jsonArray.length(); i++) { + + try { + Object jsonArrayValue = jsonArray.get(i); + + if (jsonArrayValue instanceof JSONObject) { + bundle.putBundle("" + i, handleJSONObject((JSONObject) jsonArrayValue)); + } else if (jsonArrayValue instanceof JSONArray) { + bundle.putBundle("" + i, handleJSONArray((JSONArray) jsonArrayValue)); + } else if (jsonArrayValue instanceof String) { + bundle.putString("" + i, "" + jsonArrayValue); + } else if (jsonArrayValue instanceof Boolean) { + bundle.putBoolean("" + i, (boolean) jsonArrayValue); + } else if (jsonArrayValue instanceof Integer) { + bundle.putInt("" + i, (int) jsonArrayValue); + } else if (jsonArrayValue instanceof Double) { + bundle.putDouble("" + i, (double) jsonArrayValue); + } else if (jsonArrayValue instanceof Long) { + bundle.putLong("" + i, (long) jsonArrayValue); + } + } catch (JSONException je) { + Log.d("handleJSONArray", "BAD JSON VALUE IN JSON ARRAY, AT POSITION: " + i); + } + } + + return bundle; + } + + public static Bundle handleJSONObject(JSONObject jsonObject) { + Bundle bundle = new Bundle(); + + Iterator keys = jsonObject.keys(); + + while(keys.hasNext()) { + String keyStr = keys.next(); + + try { + Object keyValue = jsonObject.get(keyStr); + + if (keyValue instanceof JSONObject) { + bundle.putBundle(keyStr, handleJSONObject((JSONObject) keyValue)); + } else if (keyValue instanceof JSONArray) { + bundle.putBundle(keyStr, handleJSONArray((JSONArray) keyValue)); + } else if (keyValue instanceof String) { + bundle.putString(keyStr, "" + keyValue); + } else if (keyValue instanceof Boolean) { + bundle.putBoolean(keyStr, (boolean) keyValue); + } else if (keyValue instanceof Integer) { + bundle.putInt(keyStr, (int) keyValue); + } else if (keyValue instanceof Double) { + bundle.putDouble(keyStr, (double) keyValue); + } else if (keyValue instanceof Long) { + bundle.putLong(keyStr, (long) keyValue); + } + } catch (JSONException je) { + Log.d("handleJSONObject", "BAD JSON VALUE IN JSON OBJECT, AT KEY: " + keyStr); + } + } + + return bundle; + } } diff --git a/Source/Fuse.PushNotifications/Common.uno b/Source/Fuse.PushNotifications/Common.uno index 0bace2a44..4106825a8 100644 --- a/Source/Fuse.PushNotifications/Common.uno +++ b/Source/Fuse.PushNotifications/Common.uno @@ -12,14 +12,13 @@ namespace Fuse.PushNotifications [ForeignInclude(Language.Java, "android.util.Log", - "com.google.firebase.iid.FirebaseInstanceId", "java.util.ArrayList", "java.util.List", "android.graphics.Color" )] [Require("Gradle.Dependency.ClassPath", "com.google.gms:google-services:4.3.2")] [Require("Gradle.AllProjects.Repository", "maven {url 'https://maven.google.com'}")] - [Require("Gradle.Dependency.Implementation", "com.google.firebase:firebase-analytics:17.2.1")] + [Require("Gradle.Dependency.Implementation", "com.google.firebase:firebase-analytics:21.3.0")] [Require("Gradle.BuildFile.End", "apply plugin: 'com.google.gms.google-services'")] public static class PushNotify { @@ -165,7 +164,12 @@ namespace Fuse.PushNotifications iOSImpl.RegisterForPushNotifications(); } - public extern(!iOS) static void Register() { } + public extern(Android) static void Register() + { + AndroidImpl.RegisterForPushNotifications(); + } + + public extern(!iOS && !Android) static void Register() { } public extern(iOS) static bool IsRegisteredForRemoteNotifications() { diff --git a/Source/Fuse.PushNotifications/Docs/Guide.md b/Source/Fuse.PushNotifications/Docs/Guide.md index fbf765af5..9105acc14 100644 --- a/Source/Fuse.PushNotifications/Docs/Guide.md +++ b/Source/Fuse.PushNotifications/Docs/Guide.md @@ -61,6 +61,11 @@ If you wish to disable auto-registration you can place the following in your uno "RegisterOnLaunch": false } }, + "Android": { + "PushNotifications": { + "RegisterOnLaunch": false + } + }, ``` You must then register for push notifications by calling `register()` from JS. This option is useful as when the notifications are registered the OS may ask the user for permission to use push notifications and this may be undesirable on launch. diff --git a/Source/Fuse.PushNotifications/Fuse.PushNotifications.unoproj b/Source/Fuse.PushNotifications/Fuse.PushNotifications.unoproj index 60547e486..0a50387f6 100644 --- a/Source/Fuse.PushNotifications/Fuse.PushNotifications.unoproj +++ b/Source/Fuse.PushNotifications/Fuse.PushNotifications.unoproj @@ -12,7 +12,8 @@ "../Fuse.Elements/Fuse.Elements.unoproj", "../Fuse.Platform/Fuse.Platform.unoproj", "../Fuse.Reactive/Fuse.Reactive.unoproj", - "../Fuse.Scripting/Fuse.Scripting.unoproj" + "../Fuse.Scripting/Fuse.Scripting.unoproj", + "../Fuse.Android.Permissions/Fuse.Android.Permissions.unoproj" ], "Includes": [ "Docs/**:File",