From 43b3b06b64739658f79c994110654f5a56abca05 Mon Sep 17 00:00:00 2001 From: Russell Wheatley Date: Wed, 24 Aug 2022 12:15:48 +0100 Subject: [PATCH] feat(firebase_messaging, android)!: android 13 notifications permission request (#9348) `android.permission.POST_NOTIFICATIONS` has been added to the FirebaseMessaging "AndroidManifest.xml" for requesting notification permission. --- .../firebase_messaging/android/build.gradle | 6 +- .../android/src/main/AndroidManifest.xml | 2 + .../FlutterFirebaseMessagingPlugin.java | 74 ++++++++++++++++--- .../FlutterFirebaseMessagingUtils.java | 5 ++ .../FlutterFirebasePermissionManager.java | 64 ++++++++++++++++ .../example/android/app/build.gradle | 4 +- .../android/app/src/main/AndroidManifest.xml | 2 +- tests/android/app/build.gradle | 4 +- .../app/FlutterMultiDexApplication.java | 11 ++- 9 files changed, 153 insertions(+), 19 deletions(-) create mode 100644 packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebasePermissionManager.java diff --git a/packages/firebase_messaging/firebase_messaging/android/build.gradle b/packages/firebase_messaging/firebase_messaging/android/build.gradle index ba7b5c381083..9840ef810731 100644 --- a/packages/firebase_messaging/firebase_messaging/android/build.gradle +++ b/packages/firebase_messaging/firebase_messaging/android/build.gradle @@ -35,7 +35,7 @@ def getRootProjectExtOrCoreProperty(name, firebaseCoreProject) { } android { - compileSdkVersion 31 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -51,8 +51,8 @@ android { api firebaseCoreProject implementation platform("com.google.firebase:firebase-bom:${getRootProjectExtOrCoreProperty("FirebaseSDKVersion", firebaseCoreProject)}") implementation 'com.google.firebase:firebase-messaging' - implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' - implementation 'androidx.annotation:annotation:1.1.0' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' + implementation 'androidx.annotation:annotation:1.4.0' } } diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml b/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml index fa64b98881f7..50bbd7c5b92a 100644 --- a/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml @@ -3,6 +3,8 @@ + + > setAutoInitEnabled(Map argumen return taskCompletionSource.getTask(); } - private Task> getInitialMessage(Map arguments) { + private Task> getInitialMessage() { TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); cachedThreadPool.execute( @@ -311,6 +315,45 @@ private Task> getInitialMessage(Map argument return taskCompletionSource.getTask(); } + @RequiresApi(api = 33) + private Task> requestPermissions() { + TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); + cachedThreadPool.execute( + () -> { + final Map permissions = new HashMap<>(); + try { + final boolean areNotificationsEnabled = checkPermissions(); + + if (!areNotificationsEnabled) { + permissionManager.requestPermissions( + mainActivity, + (notificationsEnabled) -> { + permissions.put("authorizationStatus", notificationsEnabled); + taskCompletionSource.setResult(permissions); + }, + (String errorDescription) -> { + taskCompletionSource.setException(new Exception(errorDescription)); + }); + } else { + permissions.put("authorizationStatus", 1); + taskCompletionSource.setResult(permissions); + } + + } catch (Exception e) { + taskCompletionSource.setException(e); + } + }); + + return taskCompletionSource.getTask(); + } + + @RequiresApi(api = 33) + private Boolean checkPermissions() { + return ContextHolder.getApplicationContext() + .checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) + == PackageManager.PERMISSION_GRANTED; + } + private Task> getPermissions() { TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); @@ -318,9 +361,14 @@ private Task> getPermissions() { () -> { try { final Map permissions = new HashMap<>(); - final boolean areNotificationsEnabled = - NotificationManagerCompat.from(mainActivity).areNotificationsEnabled(); - permissions.put("authorizationStatus", areNotificationsEnabled ? 1 : 0); + if (Build.VERSION.SDK_INT >= 33) { + final boolean areNotificationsEnabled = checkPermissions(); + permissions.put("authorizationStatus", areNotificationsEnabled ? 1 : 0); + } else { + final boolean areNotificationsEnabled = + NotificationManagerCompat.from(mainActivity).areNotificationsEnabled(); + permissions.put("authorizationStatus", areNotificationsEnabled ? 1 : 0); + } taskCompletionSource.setResult(permissions); } catch (Exception e) { taskCompletionSource.setException(e); @@ -379,7 +427,7 @@ public void onMethodCall(final MethodCall call, @NonNull final Result result) { methodCallTask = Tasks.forResult(null); break; case "Messaging#getInitialMessage": - methodCallTask = getInitialMessage(call.arguments()); + methodCallTask = getInitialMessage(); break; case "Messaging#deleteToken": methodCallTask = deleteToken(); @@ -400,6 +448,14 @@ public void onMethodCall(final MethodCall call, @NonNull final Result result) { methodCallTask = setAutoInitEnabled(call.arguments()); break; case "Messaging#requestPermission": + if (Build.VERSION.SDK_INT >= 33) { + // Android version >= Android 13 requires user input if notification permission not set/granted + methodCallTask = requestPermissions(); + } else { + // Android version < Android 13 doesn't require asking for runtime permissions. + methodCallTask = getPermissions(); + } + break; case "Messaging#getNotificationSettings": methodCallTask = getPermissions(); break; diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingUtils.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingUtils.java index 942357d3a36a..f873fce3be09 100644 --- a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingUtils.java +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingUtils.java @@ -16,6 +16,11 @@ import java.util.Objects; import java.util.Set; +@FunctionalInterface +interface ErrorCallback { + void onError(String errorDescription); +} + class FlutterFirebaseMessagingUtils { static final String IS_AUTO_INIT_ENABLED = "isAutoInitEnabled"; static final String SHARED_PREFERENCES_KEY = "io.flutter.firebase.messaging.callback"; diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebasePermissionManager.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebasePermissionManager.java new file mode 100644 index 000000000000..e70c7206b899 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebasePermissionManager.java @@ -0,0 +1,64 @@ +package io.flutter.plugins.firebase.messaging; + +import android.Manifest; +import android.app.Activity; +import android.content.pm.PackageManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.app.ActivityCompat; +import io.flutter.plugin.common.PluginRegistry; +import java.util.ArrayList; + +class FlutterFirebasePermissionManager implements PluginRegistry.RequestPermissionsResultListener { + + private final int permissionCode = 24; + @Nullable private RequestPermissionsSuccessCallback successCallback; + private boolean requestInProgress = false; + + @FunctionalInterface + interface RequestPermissionsSuccessCallback { + void onSuccess(int results); + } + + @Override + public boolean onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + requestInProgress = false; + if (requestCode != permissionCode) { + return false; + } + + int grantResult = grantResults[0]; + assert this.successCallback != null; + this.successCallback.onSuccess(grantResult == PackageManager.PERMISSION_GRANTED ? 1 : 0); + return true; + } + + @RequiresApi(api = 33) + public void requestPermissions( + Activity activity, + RequestPermissionsSuccessCallback successCallback, + ErrorCallback errorCallback) { + if (requestInProgress) { + errorCallback.onError( + "A request for permissions is already running, please wait for it to finish before doing another request."); + return; + } + + if (activity == null) { + errorCallback.onError("Unable to detect current Android Activity."); + return; + } + + this.successCallback = successCallback; + final ArrayList permissions = new ArrayList(); + permissions.add(Manifest.permission.POST_NOTIFICATIONS); + final String[] requestNotificationPermission = permissions.toArray(new String[0]); + + if (!requestInProgress) { + ActivityCompat.requestPermissions(activity, requestNotificationPermission, permissionCode); + requestInProgress = true; + } + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/build.gradle b/packages/firebase_messaging/firebase_messaging/example/android/app/build.gradle index 81e884bb0580..e9ad883f44cf 100644 --- a/packages/firebase_messaging/firebase_messaging/example/android/app/build.gradle +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/build.gradle @@ -25,7 +25,7 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 33 lintOptions { disable 'InvalidPackage' @@ -35,7 +35,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "io.flutter.plugins.firebase.messaging.example" minSdkVersion 19 - targetSdkVersion 31 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/AndroidManifest.xml index d2f32cdfe73a..132caa50bd8a 100644 --- a/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - @@ -10,6 +9,7 @@ android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" + android:exported="true" android:windowSoftInputMode="adjustResize">