diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
deleted file mode 100644
index 048a74fca..000000000
--- a/.github/workflows/format.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-name: format
-
-on: push
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- java_format:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: axel-op/googlejavaformat-action@v3
- with:
- args: '--skip-sorting-imports --replace'
-
- objc_format:
- runs-on: macos-latest
- steps:
- - uses: actions/checkout@v4
- - name: Test
- run: |
- which clang-format || brew install clang-format
- find . -name '*.m' -exec clang-format -i {} \;
- find . -path '*/ios/**/*.h' -exec clang-format -i {} \;
- find . -path '*/macos/**/*.h' -exec clang-format -i {} \;
- git diff --exit-code || (git commit --all -m "Clang Format" && git push)
-
- swift_format:
- runs-on: macos-latest
- steps:
- - uses: actions/checkout@v4
- - name: Test
- run: |
- which swiftlint || brew install swiftlint
- swiftlint --fix
- git diff --exit-code || (git commit --all -m "Swift Format" && git push)
diff --git a/.github/workflows/scripts/install-tools.sh b/.github/workflows/scripts/install-tools.sh
index 31760cf0d..5c08b7720 100755
--- a/.github/workflows/scripts/install-tools.sh
+++ b/.github/workflows/scripts/install-tools.sh
@@ -1,2 +1,2 @@
-dart pub global activate melos 2.9.0
+dart pub global activate melos
melos bootstrap
\ No newline at end of file
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 956d11b1b..a5a64857b 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -2,15 +2,49 @@ name: validate
on:
pull_request:
+ paths-ignore:
+ - '**.md'
push:
branches:
- master
+ paths-ignore:
+ - '**.md'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
+ java_format:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: axel-op/googlejavaformat-action@v3
+ with:
+ args: '--skip-sorting-imports --replace'
+
+ objc_format:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Test
+ run: |
+ which clang-format || brew install clang-format
+ find . -name '*.m' -exec clang-format -i {} \;
+ find . -path '*/ios/**/*.h' -exec clang-format -i {} \;
+ find . -path '*/macos/**/*.h' -exec clang-format -i {} \;
+ git diff --exit-code || (git commit --all -m "Clang Format" && git push)
+
+ swift_format:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Test
+ run: |
+ which swiftlint || brew install swiftlint
+ swiftlint --fix
+ git diff --exit-code || (git commit --all -m "Swift Format" && git push)
+
analyze:
name: Run Dart Analyzer
runs-on: ubuntu-latest
@@ -42,29 +76,44 @@ jobs:
run: ./.github/workflows/scripts/install-tools.sh
- name: Build
run: melos run build:example_android
- build_example_android_300:
- name: Build Android example app (3.0.0)
- runs-on: ubuntu-latest
+ build_example_android_3_13:
+ name: Build Android example app (3.13)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+ flutter-version: 3.13.0
+ cache: true
+ cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+ - name: Install Tools
+ run: ./.github/workflows/scripts/install-tools.sh
+ - name: Build
+ run: melos run build:example_android
+ build_example_ios_stable:
+ name: Build iOS example app (stable channel)
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
- flutter-version: 3.0.0
channel: stable
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
run: ./.github/workflows/scripts/install-tools.sh
- name: Build
- run: melos run build:example_android
- build_example_ios_stable:
- name: Build iOS example app (stable channel)
+ run: melos run build:example_ios
+ build_example_ios_3_13:
+ name: Build iOS example app (3.13)
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
+ flutter-version: 3.13.0
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
@@ -85,6 +134,21 @@ jobs:
run: ./.github/workflows/scripts/install-tools.sh
- name: Build
run: melos run build:example_macos
+ build_example_macos_3_13:
+ name: Build macOS example app (3.13)
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+ flutter-version: 3.13.0
+ cache: true
+ cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+ - name: Install Tools
+ run: ./.github/workflows/scripts/install-tools.sh
+ - name: Build
+ run: melos run build:example_macos
build_example_linux_stable:
name: Build Linux example app (stable channel)
runs-on: ubuntu-latest
@@ -103,15 +167,15 @@ jobs:
- run: flutter config --enable-linux-desktop
- name: Build
run: melos run build:example_linux
- build_example_linux_300:
- name: Build Linux example app (3.0.0)
+ build_example_linux_3_13:
+ name: Build Linux example app (3.13)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
- flutter-version: 3.0.0
channel: stable
+ flutter-version: 3.13.0
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
@@ -152,7 +216,7 @@ jobs:
run: melos run test:unit:android
integration_tests_android:
name: Run integration tests (Android)
- runs-on: macos-latest
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
@@ -166,6 +230,11 @@ jobs:
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Install Tools
run: ./.github/workflows/scripts/install-tools.sh
+ - name: Enable KVM
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
@@ -185,7 +254,7 @@ jobs:
- uses: futureware-tech/simulator-action@v3
id: simulator-action
with:
- model: 'iPhone 13'
+ model: 'iPhone 15'
- run: |
brew tap wix/brew
brew install applesimutils
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 3605ad5cb..343acd642 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -3,7 +3,6 @@ linter:
- always_declare_return_types
- always_put_control_body_on_new_line
- always_put_required_named_parameters_first
- - always_require_non_null_named_parameters
- always_specify_types
- annotate_overrides
- avoid_annotating_with_dynamic
@@ -29,8 +28,6 @@ linter:
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
- - avoid_returning_null
- - avoid_returning_null_for_future
- avoid_returning_null_for_void
- avoid_returning_this
- avoid_setters_without_getters
@@ -62,7 +59,6 @@ linter:
- flutter_style_todos
- hash_and_equals
- implementation_imports
- - invariant_booleans
- collection_methods_unrelated_type
- join_return_with_assignment
- leading_newlines_in_multiline_strings
@@ -95,7 +91,6 @@ linter:
- prefer_const_literals_to_create_immutables
- prefer_constructors_over_static_methods
- prefer_contains
- - prefer_equal_for_default_values
- prefer_expression_function_bodies
- prefer_final_fields
- prefer_final_in_for_each
diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md
index e146be554..02c08011b 100644
--- a/flutter_local_notifications/CHANGELOG.md
+++ b/flutter_local_notifications/CHANGELOG.md
@@ -1,3 +1,59 @@
+## [vNext]
+
+* **Breaking change** Bumped minimum Flutter SDK requirement to 3.13
+* [Android] **Breaking change** removed the deprecated `androidAllowWhileIdle` parameter from `zonedSchedule()` and `periodicallyShow()` methods. `androidScheduleMode` is now a required parameter
+
+## [17.2.1+2]
+
+* Updated Gradle setup readme section around specifying AGP version to include link to Flutter documentation for apps that are using the declarative Plugin DSL syntax
+
+## [17.2.1+1]
+
+* Fixed accidental change done in example app as part of 17.2.0 where it made use of `SCHEDULE_EXACT_ALARM` permission instead of `USE_EXACT_ALARM`
+
+## [17.2.1]
+
+* [Android] fixed issue [#2329](https://github.com/MaikuB/flutter_local_notifications/issues/2329) where a compilation issue could occur due to ambiguity between Android APIs being called. Thanks to the PR from [Greg Price](https://github.com/gnprice)
+
+## [17.2.0]
+
+* [Android][iOS][macOS] added `periodicallyShowWithDuration()` method that allows for having a notification periodically shown based on a specified duration. The duration will need to be at least a minute. Thanks to the PR from [Mateusz Łuczak](https://github.com/mateuszluczak1996)
+* [Android] added the `requestFullScreenIntentPermission()` to the `AndroidFlutterNotificationsPlugin` class. This allows app to request the full-screen intent permission. Updated the documentation around full-screen intent notifications accordingly as well
+* Added a comment to the `AndroidManifest.xml` file of the example to state that it requests the `USE_EXACT_ALARM` only for ease of use. Developers will need to check if they should be using the `SCHEDULE_EXACT_ALARM` permission instead
+
+## [17.1.2]
+
+* [Android] fixed issue [2318](https://github.com/MaikuB/flutter_local_notifications/issues/2318) where an exception could occur on calling the `getNotificationChannels()` method from the `AndroidFlutterLocalNotificationsPlugin` class. This happened when Android found that the audio attributes associated with the channel was null. The plugin will now coalesce the null value to indicate that the usage of the audio is for notifications as per https://developer.android.com/reference/android/media/AudioAttributes#USAGE_NOTIFICATION. On the Dart side, this would correspond to the [AudioAttributesUsage.notification](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AudioAttributesUsage.html#notification) enum value
+
+## [17.1.1]
+
+* [Android] fixes issue [#2299](https://github.com/MaikuB/flutter_local_notifications/issues/2299) where within the range of the max integer value of epoch time passed to a messaging style would result in a casting exception
+
+## [17.1.0]
+
+* [Android] `bigText` has added to `ActiveNotification` that allows getting information about the longer text associated with a notification displayed using the big text style. Thanks to the PR from [vulpeep](https://github.com/vulpeep)
+* [Android] added `audioAttributesUsage` to `AndroidNotificationChannel`. Thanks to the PR from [Dithesh](https://github.com/ditheshthegreat)
+* Fix description of the behaviour iOS pending notifications limit. Thanks to the PR from [Amman Zaman](https://github.com/zamanzamzz)
+* Updated link in readme to Gradle desugaring setup. Thanks to the PR from [James Allen](https://github.com/jamesncl)
+
+
+# [17.0.1]
+
+* [iOS] updated privacy manifest to declare reason the plugin uses the User Defaults API. Thanks to the PR from [Miya49-p0](https://github.com/Miya49-p0)
+
+# [17.0.0]
+
+* [Android] **Breaking change** bumped `compileSdk` to 34 and updated readme to mention this
+* Updated `compileSdk` and `targetSdkVersion` of example app to 34
+* **Important announcement** given how both quickly both Flutter ecosystem and Android ecosystem evolves, the minimum Flutter SDK version will be bumped to make it easier to maintain the plugin. Note that official plugins already follow a similar approach e.g. have a minimum Flutter SDK version of 3.13. This is being called out as if this affects your applications (e.g. supported OS versions) then you may need to consider maintaining your own fork in the future
+* Updated build status badge shown on readme to sync to recent changes on using GitHub Actions
+* Fixed code snippet in readme related to handling the `onDidReceiveLocalNotification` callback. Thanks to the PR from [Sanket Patel](https://github.com/s4nk37)
+
+# [16.3.3]
+
+* [Android] added missing check on if `SCHEDULE_EXACT_ALARM` permission was granted when using the `alarmClock` as the `AndroidScheduleMode`
+* Bumped `device_info_plus` dependency for example app, which means example app requires Flutter SDK version 3.3.0 or higher to run
+
# [16.3.2]
* [Android] fixed how native stack traces were obtained. Relates to issue [2088](https://github.com/MaikuB/flutter_local_notifications/issues/2088). Thanks to the PR from [Jonas Uekötter](https://github.com/ueman)
diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md
index 927a05fdd..2619472e8 100644
--- a/flutter_local_notifications/README.md
+++ b/flutter_local_notifications/README.md
@@ -1,10 +1,13 @@
# flutter_local_notifications
[![pub package](https://img.shields.io/pub/v/flutter_local_notifications.svg)](https://pub.dartlang.org/packages/flutter_local_notifications)
-[![Build Status](https://api.cirrus-ci.com/github/MaikuB/flutter_local_notifications.svg)](https://cirrus-ci.com/github/MaikuB/flutter_local_notifications/master)
+![Build Status](https://github.com/MaikuB/flutter_local_notifications/actions/workflows/validate.yml/badge.svg)
A cross platform plugin for displaying local notifications.
+>[!IMPORTANT]
+> Given how both quickly both Flutter ecosystem and Android ecosystem evolves, the minimum Flutter SDK version will be bumped to make it easier to maintain the plugin. Note that official plugins already follow a similar approach e.g. have a minimum Flutter SDK version of 3.13. This is being called out as if this affects your applications (e.g. supported OS versions) then you may need to consider maintaining your own fork in the future
+
## Table of contents
- **[📱 Supported platforms](#-supported-platforms)**
@@ -52,10 +55,12 @@ A cross platform plugin for displaying local notifications.
## 📱 Supported platforms
-* **Android 4.1+**. Uses the [NotificationCompat APIs](https://developer.android.com/reference/androidx/core/app/NotificationCompat) so it can be run older Android devices
-* **iOS 8.0+**. On iOS versions older than 10, the plugin will use the UILocalNotification APIs. The [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) is used on iOS 10 or newer. Notification actions only work on iOS 10 or newer.
-* **macOS 10.11+**. On macOS versions older than 10.14, the plugin will use the [NSUserNotification APIs](https://developer.apple.com/documentation/foundation/nsusernotification). The [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) is used on macOS 10.14 or newer. Notification actions only work on macOS 10.14 or newer
-* **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/).
+* **Android+**. Uses the [NotificationCompat APIs](https://developer.android.com/reference/androidx/core/app/NotificationCompat) so it can be run older Android devices
+* **iOS**. Uses the [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework)
+* **macOS**. On macOS versions older than 10.14, the plugin will use the [NSUserNotification APIs](https://developer.apple.com/documentation/foundation/nsusernotification). The [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) is used on macOS 10.14 or newer. Notification actions only work on macOS 10.14 or newer
+* **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/)
+
+Note: the plugin has a requires Flutter SDK 3.13 at a minimum. The list of support platforms for Flutter 3.1.3 itself can be found [here](https://github.com/flutter/website/blob/3d18ab48218101493af84953b71eac0cc6781fdd/src/reference/supported-platforms.md)
## ✨ Features
@@ -121,7 +126,7 @@ It has been reported that Samsung's implementation of Android has imposed a maxi
### iOS pending notifications limit
-There is a limit imposed by iOS where it will only keep 64 notifications that will fire the soonest.
+There is a limit imposed by iOS where it will only keep the 64 notifications that were last set on any iOS versions newer than 9. On iOS versions 9 and older, the 64 notifications that fire soonest are kept. [See here for more details.](http://ileyf.cn.openradar.appspot.com/38065340)
### Scheduled notifications and daylight saving time
@@ -177,7 +182,7 @@ Before proceeding, please make sure you are using the latest version of the plug
### Gradle setup
-Version 10+ on the plugin now relies on [desugaring](https://developer.android.com/studio/releases/gradle-plugin#j8-library-desugaring) to support scheduled notifications with backwards compatibility on older versions of Android. Developers will need to update their application's Gradle file at `android/app/build.gradle`. Please see the link on desugaring for details but the main parts needed in this Gradle file would be
+Version 10+ on the plugin now relies on [desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) to support scheduled notifications with backwards compatibility on older versions of Android. Developers will need to update their application's Gradle file at `android/app/build.gradle`. Please see the link on desugaring for details but the main parts needed in this Gradle file would be
```gradle
android {
@@ -199,7 +204,7 @@ dependencies {
}
```
-Note that the plugin uses Android Gradle plugin 7.3.1 to leverage this functionality so to errr on the safe side, applications should aim to use the same version at a **minimum**. Using a higher version is also needed as at point, Android Studio bundled a newer version of the Java SDK that will only work with Gradle 7.3 or higher (see [here](https://docs.flutter.dev/release/breaking-changes/android-java-gradle-migration-guide) for more details). For a Flutter project, this is specified in `android/build.gradle` and the main parts would look similar to the following
+Note that the plugin uses Android Gradle plugin (AGP) 7.3.1 to leverage this functionality so to errr on the safe side, applications should aim to use the same version at a **minimum**. Using a higher version is also needed as at point, Android Studio bundled a newer version of the Java SDK that will only work with Gradle 7.3 or higher (see [here](https://docs.flutter.dev/release/breaking-changes/android-java-gradle-migration-guide) for more details). For a Flutter app usin the legacy `apply` script syntax, this is specified in `android/build.gradle` and the main parts would look similar to the following
```gradle
buildscript {
@@ -211,6 +216,8 @@ buildscript {
}
```
+If your app is using the new declarative Plugin DSL syntax, please refer to the Flutter documentation [here](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) where they document where the AGP version can be specified
+
There have been reports that enabling desugaring may result in a Flutter apps crashing on Android 12L and above. This would be an issue with Flutter itself, not the plugin. One possible fix is adding the [WindowManager library](https://developer.android.com/jetpack/androidx/releases/window) as a dependency:
```gradle
@@ -223,11 +230,11 @@ dependencies {
More information and other proposed solutions can be found in [Flutter issue #110658](https://github.com/flutter/flutter/issues/110658).
-The plugin also requires that the `compileSdkVersion` in your application's Gradle file is set to 33:
+The plugin also requires that the `compileSdk` in your application's Gradle file is set to 34 at a minimum:
```gradle
android {
- compileSdkVersion 33
+ compileSdk 34
...
}
```
@@ -256,7 +263,7 @@ For apps that need the following functionality please complete the following in
```
-* To use full-screen intent notifications, specify the `` permission between the `` tags.
+* To use full-screen intent notifications, specify the `` permission between the `` tags. Developers will also need to follow the instructions documented [here](#full-screen-intent-notifications)
* To use notification actions, specify `` between the `` tags so that the plugin can process the actions and trigger the appropriate callback(s)
Developers can refer to the example app's `AndroidManifest.xml` to help see what the end result may look like. Do note that the example app covers all the plugin's supported functionality so will request more permissions than your own app may need
@@ -298,6 +305,8 @@ For reference, the example app's `AndroidManifest.xml` file can be found [here](
Note that when a full-screen intent notification actually occurs (as opposed to a heads-up notification that the system may decide should occur), the plugin will act as though the user has tapped on a notification so handle those the same way (e.g. `onDidReceiveNotificationResponse` callback) to display the appropriate page for your application.
+Developers should also be across Google's requirements on using full-screen intents. Please refer to their documentation [here](https://source.android.com/docs/core/permissions/fsi-limits) for more information. Should you app need request permissions, the `AndroidFlutterNotificationsPlugin` class exposes the `requestFullScreenIntentPermission()` method that can be used to do so.
+
### Release build configuration
Before creating the release build of your app (which is the default setting when building an APK or app bundle) you will need to customise your ProGuard configuration file as per this [link](https://developer.android.com/studio/build/shrink-code#keep-code). Rules specific to the GSON dependency being used by the plugin will need to be added. These rules can be found [here](https://github.com/google/gson/blob/master/examples/android-proguard-example/proguard.cfg). Whilst the example app has a Proguard rules (`proguard-rules.pro`) [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/proguard-rules.pro), it is recommended that developers refer to the rules on the GSON repository in case they get updated over time.
@@ -354,13 +363,13 @@ flutterLocalNotificationsPlugin.initialize(initializationSettings,
...
void onDidReceiveLocalNotification(
- int id, String title?, String? body, String? payload) async {
+ int id, String? title, String? body, String? payload) async {
// display a dialog with the notification details, tap ok to go to another page
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
- title: Text(title),
- content: Text(body),
+ title: Text(title??''),
+ content: Text(body??''),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
@@ -744,7 +753,7 @@ const NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
await flutterLocalNotificationsPlugin.periodicallyShow(0, 'repeating title',
'repeating body', RepeatInterval.everyMinute, notificationDetails,
- androidAllowWhileIdle: true);
+ androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle);
```
### Retrieving pending notification requests
diff --git a/flutter_local_notifications/android/build.gradle b/flutter_local_notifications/android/build.gradle
index 843a4807d..6d872a171 100644
--- a/flutter_local_notifications/android/build.gradle
+++ b/flutter_local_notifications/android/build.gradle
@@ -23,7 +23,7 @@ apply plugin: 'com.android.library'
android {
namespace 'com.dexterous.flutterlocalnotifications'
- compileSdkVersion 33
+ compileSdk 34
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
index f34dec658..b0968614b 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
@@ -1,5 +1,6 @@
package com.dexterous.flutterlocalnotifications;
+import static android.provider.Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT;
import static android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM;
import android.Manifest;
@@ -156,12 +157,16 @@ public class FlutterLocalNotificationsPlugin
private static final String CANCEL_ALL_METHOD = "cancelAll";
private static final String ZONED_SCHEDULE_METHOD = "zonedSchedule";
private static final String PERIODICALLY_SHOW_METHOD = "periodicallyShow";
+ private static final String PERIODICALLY_SHOW_WITH_DURATION = "periodicallyShowWithDuration";
private static final String GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD =
"getNotificationAppLaunchDetails";
private static final String REQUEST_NOTIFICATIONS_PERMISSION_METHOD =
"requestNotificationsPermission";
private static final String REQUEST_EXACT_ALARMS_PERMISSION_METHOD =
"requestExactAlarmsPermission";
+
+ private static final String REQUEST_FULL_SCREEN_INTENT_PERMISSION_METHOD =
+ "requestFullScreenIntentPermission";
private static final String METHOD_CHANNEL = "dexterous.com/flutter/local_notifications";
private static final String INVALID_ICON_ERROR_CODE = "invalid_icon";
private static final String INVALID_LARGE_ICON_ERROR_CODE = "invalid_large_icon";
@@ -204,6 +209,8 @@ public class FlutterLocalNotificationsPlugin
static final int EXACT_ALARM_PERMISSION_REQUEST_CODE = 2;
+ static final int FULL_SCREEN_INTENT_PERMISSION_REQUEST_CODE = 3;
+
private PermissionRequestListener callback;
private PermissionRequestProgress permissionRequestProgress = PermissionRequestProgress.None;
@@ -212,7 +219,8 @@ static void rescheduleNotifications(Context context) {
ArrayList scheduledNotifications = loadScheduledNotifications(context);
for (NotificationDetails notificationDetails : scheduledNotifications) {
try {
- if (notificationDetails.repeatInterval != null) {
+ if (notificationDetails.repeatInterval != null
+ || notificationDetails.repeatIntervalMilliseconds != null) {
repeatNotification(context, notificationDetails, false);
} else if (notificationDetails.timeZoneName != null) {
zonedScheduleNotification(context, notificationDetails, false);
@@ -232,7 +240,8 @@ static void scheduleNextNotification(Context context, NotificationDetails notifi
zonedScheduleNextNotification(context, notificationDetails);
} else if (notificationDetails.matchDateTimeComponents != null) {
zonedScheduleNextNotificationMatchingDateComponents(context, notificationDetails);
- } else if (notificationDetails.repeatInterval != null) {
+ } else if (notificationDetails.repeatInterval != null
+ || notificationDetails.repeatIntervalMilliseconds != null) {
scheduleNextRepeatingNotification(context, notificationDetails);
} else {
removeNotificationFromCache(context, notificationDetails.id);
@@ -721,6 +730,7 @@ private static void setupAlarm(
AlarmManagerCompat.setExact(
alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent);
} else if (notificationDetails.scheduleMode.useAlarmClock()) {
+ checkCanScheduleExactAlarms(alarmManager);
AlarmManagerCompat.setAlarmClock(alarmManager, epochMilli, pendingIntent, pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent);
@@ -738,6 +748,7 @@ private static void setupAllowWhileIdleAlarm(
AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent);
} else if (notificationDetails.scheduleMode.useAlarmClock()) {
+ checkCanScheduleExactAlarms(alarmManager);
AlarmManagerCompat.setAlarmClock(alarmManager, epochMilli, pendingIntent, pendingIntent);
} else {
AlarmManagerCompat.setAndAllowWhileIdle(
@@ -763,6 +774,12 @@ private static long calculateNextNotificationTrigger(
private static long calculateRepeatIntervalMilliseconds(NotificationDetails notificationDetails) {
long repeatInterval = 0;
+
+ if (notificationDetails.repeatIntervalMilliseconds != null) {
+ repeatInterval = notificationDetails.repeatIntervalMilliseconds;
+ return repeatInterval;
+ }
+
switch (notificationDetails.repeatInterval) {
case EveryMinute:
repeatInterval = 60000;
@@ -1032,7 +1049,7 @@ private static void setBigPictureStyle(
}
if (bigPictureStyleInformation.hideExpandedLargeIcon) {
- bigPictureStyle.bigLargeIcon(null);
+ bigPictureStyle.bigLargeIcon((Bitmap) null);
} else {
if (bigPictureStyleInformation.largeIcon != null) {
bigPictureStyle.bigLargeIcon(
@@ -1445,9 +1462,26 @@ public void fail(String message) {
}
});
break;
+ case REQUEST_FULL_SCREEN_INTENT_PERMISSION_METHOD:
+ requestFullScreenIntentPermission(
+ new PermissionRequestListener() {
+ @Override
+ public void complete(boolean granted) {
+ result.success(granted);
+ }
+
+ @Override
+ public void fail(String message) {
+ result.error(PERMISSION_REQUEST_IN_PROGRESS_ERROR_CODE, message, null);
+ }
+ });
+ break;
case PERIODICALLY_SHOW_METHOD:
repeat(call, result);
break;
+ case PERIODICALLY_SHOW_WITH_DURATION:
+ repeat(call, result);
+ break;
case CANCEL_METHOD:
cancel(call, result);
break;
@@ -1536,6 +1570,8 @@ private void getActiveNotifications(Result result) {
activeNotificationPayload.put(
"title", notification.extras.getCharSequence("android.title"));
activeNotificationPayload.put("body", notification.extras.getCharSequence("android.text"));
+ activeNotificationPayload.put(
+ "bigText", notification.extras.getCharSequence("android.bigText"));
activeNotificationsPayload.add(activeNotificationPayload);
}
result.success(activeNotificationsPayload);
@@ -1822,6 +1858,36 @@ public void requestExactAlarmsPermission(@NonNull PermissionRequestListener call
}
}
+ public void requestFullScreenIntentPermission(@NonNull PermissionRequestListener callback) {
+ if (permissionRequestProgress != PermissionRequestProgress.None) {
+ callback.fail(PERMISSION_REQUEST_IN_PROGRESS_ERROR_MESSAGE);
+ return;
+ }
+
+ this.callback = callback;
+
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ NotificationManager notificationManager =
+ (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ AlarmManager alarmManager = getAlarmManager(applicationContext);
+ boolean permissionGranted = notificationManager.canUseFullScreenIntent();
+
+ if (!permissionGranted) {
+ permissionRequestProgress = PermissionRequestProgress.RequestingFullScreenIntentPermission;
+ mainActivity.startActivityForResult(
+ new Intent(
+ ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT,
+ Uri.parse("package:" + applicationContext.getPackageName())),
+ FULL_SCREEN_INTENT_PERMISSION_REQUEST_CODE);
+ } else {
+ this.callback.complete(true);
+ permissionRequestProgress = PermissionRequestProgress.None;
+ }
+ } else {
+ this.callback.complete(true);
+ }
+ }
+
@Override
public boolean onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
@@ -2080,6 +2146,12 @@ private HashMap getMappedNotificationChannel(NotificationChannel
channelPayload.put("vibrationPattern", channel.getVibrationPattern());
channelPayload.put("enableLights", channel.shouldShowLights());
channelPayload.put("ledColor", channel.getLightColor());
+ final AudioAttributes audioAttributes = channel.getAudioAttributes();
+ channelPayload.put(
+ "audioAttributesUsage",
+ audioAttributes == null
+ ? AudioAttributes.USAGE_NOTIFICATION
+ : audioAttributes.getUsage());
}
return channelPayload;
}
@@ -2148,7 +2220,8 @@ private void setCanScheduleExactNotifications(Result result) {
@Override
public boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode != NOTIFICATION_PERMISSION_REQUEST_CODE
- && requestCode != EXACT_ALARM_PERMISSION_REQUEST_CODE) {
+ && requestCode != EXACT_ALARM_PERMISSION_REQUEST_CODE
+ && requestCode != FULL_SCREEN_INTENT_PERMISSION_REQUEST_CODE) {
return false;
}
@@ -2160,6 +2233,15 @@ public boolean onActivityResult(int requestCode, int resultCode, @Nullable Inten
permissionRequestProgress = PermissionRequestProgress.None;
}
+ if (permissionRequestProgress == PermissionRequestProgress.RequestingFullScreenIntentPermission
+ && requestCode == FULL_SCREEN_INTENT_PERMISSION_REQUEST_CODE
+ && VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ NotificationManager notificationManager =
+ (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ this.callback.complete(notificationManager.canUseFullScreenIntent());
+ permissionRequestProgress = PermissionRequestProgress.None;
+ }
+
return true;
}
@@ -2181,6 +2263,7 @@ public ExactAlarmPermissionException() {
enum PermissionRequestProgress {
None,
RequestingNotificationPermission,
- RequestingExactAlarmsPermission
+ RequestingExactAlarmsPermission,
+ RequestingFullScreenIntentPermission
}
}
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
index a42c084a1..dcb59648a 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
@@ -30,6 +30,7 @@ public class NotificationDetails implements Serializable {
private static final String MILLISECONDS_SINCE_EPOCH = "millisecondsSinceEpoch";
private static final String CALLED_AT = "calledAt";
private static final String REPEAT_INTERVAL = "repeatInterval";
+ private static final String REPEAT_INTERVAL_MILLISECONDS = "repeatIntervalMilliseconds";
private static final String REPEAT_TIME = "repeatTime";
private static final String PLATFORM_SPECIFICS = "platformSpecifics";
private static final String AUTO_CANCEL = "autoCancel";
@@ -145,6 +146,7 @@ public class NotificationDetails implements Serializable {
public NotificationStyle style;
public StyleInformation styleInformation;
public RepeatInterval repeatInterval;
+ public Integer repeatIntervalMilliseconds;
public Time repeatTime;
public Long millisecondsSinceEpoch;
public Long calledAt;
@@ -226,6 +228,10 @@ public static NotificationDetails from(Map arguments) {
notificationDetails.repeatInterval =
RepeatInterval.values()[(Integer) arguments.get(REPEAT_INTERVAL)];
}
+ if (arguments.containsKey(REPEAT_INTERVAL_MILLISECONDS)) {
+ notificationDetails.repeatIntervalMilliseconds =
+ (Integer) arguments.get(REPEAT_INTERVAL_MILLISECONDS);
+ }
if (arguments.containsKey(REPEAT_TIME)) {
@SuppressWarnings("unchecked")
Map repeatTimeParams = (Map) arguments.get(REPEAT_TIME);
@@ -458,7 +464,7 @@ private static ArrayList readMessages(ArrayList