expo-notifications 58.0.11 → 58.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/notifications/Exceptions.kt +6 -0
- package/android/src/main/java/expo/modules/notifications/notifications/ArgumentsNotificationContentBuilder.java +8 -1
- package/android/src/main/java/expo/modules/notifications/notifications/NotificationSerializer.java +4 -0
- package/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt +1 -0
- package/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java +20 -0
- package/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt +3 -0
- package/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt +3 -0
- package/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt +5 -0
- package/android/src/main/java/expo/modules/notifications/permissions/NotificationPermissionRecords.kt +40 -0
- package/android/src/main/java/expo/modules/notifications/permissions/NotificationPermissionsModule.kt +91 -34
- package/android/src/main/java/expo/modules/notifications/service/NotificationsService.kt +38 -0
- package/android/src/main/java/expo/modules/notifications/service/delegates/ExpoPresentationDelegate.kt +145 -20
- package/android/src/main/java/expo/modules/notifications/service/interfaces/PresentationDelegate.kt +2 -0
- package/build/NotificationPermissions.d.ts +29 -0
- package/build/NotificationPermissions.js +49 -1
- package/build/NotificationPermissions.js.map +1 -1
- package/build/NotificationPermissionsModule.types.d.ts +3 -0
- package/build/NotificationPermissionsModule.types.js.map +1 -1
- package/build/Notifications.types.d.ts +9 -2
- package/build/Notifications.types.js.map +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{58.0.11/expo.modules.notifications-58.0.11-sources.jar → 58.1.1/expo.modules.notifications-58.1.1-sources.jar} +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.aar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{58.0.11/expo.modules.notifications-58.0.11.module → 58.1.1/expo.modules.notifications-58.1.1.module} +22 -22
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.module.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/{58.0.11/expo.modules.notifications-58.0.11.pom → 58.1.1/expo.modules.notifications-58.1.1.pom} +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.1.1/expo.modules.notifications-58.1.1.pom.sha512 +1 -0
- package/package.json +8 -8
- package/plugin/build/withNotifications.d.ts +9 -0
- package/plugin/build/withNotifications.js.map +1 -1
- package/plugin/build/withNotificationsIOS.js +15 -7
- package/plugin/build/withNotificationsIOS.js.map +1 -1
- package/plugin/src/withNotifications.ts +10 -0
- package/plugin/src/withNotificationsIOS.ts +26 -7
- package/src/NotificationPermissions.ts +57 -1
- package/src/NotificationPermissionsModule.types.ts +3 -0
- package/src/Notifications.types.ts +9 -2
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.notifications/58.0.11/expo.modules.notifications-58.0.11.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 58.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [Android] Add support for grouping notifications via `threadIdentifier`. ([#43388](https://github.com/expo/expo/pull/43388) by [@vonovak](https://github.com/vonovak))
|
|
8
|
+
- [Android] Add `getExactAlarmPermissionsAsync` and `requestExactAlarmPermissionsAsync` to check and request the exact alarm permission. ([#50919](https://github.com/expo/expo/pull/50919) by [@vonovak](https://github.com/vonovak))
|
|
9
|
+
- [Internal] [Android] Return notification permission responses as records instead of bundles. ([#51060](https://github.com/expo/expo/pull/51060) by [@vonovak](https://github.com/vonovak))
|
|
10
|
+
- Updated dependencies. ([#51077](https://github.com/expo/expo/pull/51077))
|
|
11
|
+
- expo-application@58.0.4
|
|
12
|
+
- @expo/image-utils@0.12.4
|
|
13
|
+
- expo-constants@58.0.10
|
|
14
|
+
|
|
15
|
+
## 58.1.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- Add `enableRemoteNotifications` config plugin option. Set it to `false` to skip the APNs entitlement for apps that use only local notifications. Defaults to `true`. ([#50891](https://github.com/expo/expo/pull/50891) by [@netmaxt3r](https://github.com/netmaxt3r))
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies. ([#50391](https://github.com/expo/expo/pull/50391))
|
|
24
|
+
- @expo/image-utils@0.12.3
|
|
25
|
+
|
|
3
26
|
## 58.0.11
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/android/build.gradle
CHANGED
|
@@ -5,13 +5,13 @@ plugins {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
group = 'host.exp.exponent'
|
|
8
|
-
version = '58.
|
|
8
|
+
version = '58.1.1'
|
|
9
9
|
|
|
10
10
|
android {
|
|
11
11
|
namespace "expo.modules.notifications"
|
|
12
12
|
defaultConfig {
|
|
13
13
|
versionCode 21
|
|
14
|
-
versionName '58.
|
|
14
|
+
versionName '58.1.1'
|
|
15
15
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
16
16
|
consumerProguardFiles 'proguard-rules.pro'
|
|
17
17
|
}
|
|
@@ -7,3 +7,9 @@ class ModuleNotFoundException(moduleClass: KClass<*>) :
|
|
|
7
7
|
CodedException(message = "$moduleClass module not found")
|
|
8
8
|
|
|
9
9
|
class NotificationWasAlreadyHandledException(val id: String) : CodedException("Failed to handle notification $id, it has already been handled.")
|
|
10
|
+
|
|
11
|
+
class ExactAlarmPermissionNotDeclaredException :
|
|
12
|
+
CodedException(
|
|
13
|
+
"Cannot request the exact alarm permission because the app does not declare android.permission.SCHEDULE_EXACT_ALARM. " +
|
|
14
|
+
"Add it to `android.permissions` in your app config and rebuild the app."
|
|
15
|
+
)
|
|
@@ -29,6 +29,7 @@ public class ArgumentsNotificationContentBuilder extends NotificationContent.Bui
|
|
|
29
29
|
private static final String AUTO_DISMISS_KEY = "autoDismiss";
|
|
30
30
|
private static final String CATEGORY_IDENTIFIER_KEY = "categoryIdentifier";
|
|
31
31
|
private static final String STICKY_KEY = "sticky";
|
|
32
|
+
private static final String THREAD_IDENTIFIER_KEY = "threadIdentifier";
|
|
32
33
|
|
|
33
34
|
private SoundResolver mSoundResolver;
|
|
34
35
|
|
|
@@ -46,7 +47,8 @@ public class ArgumentsNotificationContentBuilder extends NotificationContent.Bui
|
|
|
46
47
|
.setColor(getColor(payload))
|
|
47
48
|
.setAutoDismiss(getAutoDismiss(payload))
|
|
48
49
|
.setCategoryId(getCategoryId(payload))
|
|
49
|
-
.setSticky(getSticky(payload))
|
|
50
|
+
.setSticky(getSticky(payload))
|
|
51
|
+
.setGroup(getGroup(payload));
|
|
50
52
|
|
|
51
53
|
if (shouldPlayDefaultSound(payload)) {
|
|
52
54
|
useDefaultSound();
|
|
@@ -146,4 +148,9 @@ public class ArgumentsNotificationContentBuilder extends NotificationContent.Bui
|
|
|
146
148
|
// TODO: the default value should be determined by NotificationContent.Builder
|
|
147
149
|
return payload.getBoolean(STICKY_KEY, false);
|
|
148
150
|
}
|
|
151
|
+
|
|
152
|
+
@Nullable
|
|
153
|
+
protected String getGroup(ReadableArguments payload) {
|
|
154
|
+
return payload.getString(THREAD_IDENTIFIER_KEY, null);
|
|
155
|
+
}
|
|
149
156
|
}
|
package/android/src/main/java/expo/modules/notifications/notifications/NotificationSerializer.java
CHANGED
|
@@ -129,6 +129,7 @@ public class NotificationSerializer {
|
|
|
129
129
|
serializedContent.putString("categoryIdentifier", content.getCategoryId());
|
|
130
130
|
}
|
|
131
131
|
serializedContent.putBoolean("sticky", content.isSticky());
|
|
132
|
+
serializedContent.putString("threadIdentifier", content.getGroup());
|
|
132
133
|
return serializedContent;
|
|
133
134
|
}
|
|
134
135
|
|
|
@@ -187,6 +188,9 @@ public class NotificationSerializer {
|
|
|
187
188
|
serializedContent.putBundle("data", filteredBundleForJSTypeConverter(extras));
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
String threadIdentifier = extras.getString("threadIdentifier");
|
|
192
|
+
serializedContent.putString("threadIdentifier", threadIdentifier == null || threadIdentifier.isEmpty() ? null : threadIdentifier);
|
|
193
|
+
|
|
190
194
|
Bundle serializedTrigger = new Bundle();
|
|
191
195
|
serializedTrigger.putString("type", "push");
|
|
192
196
|
serializedTrigger.putString("channelId", extras.getString("channelId"));
|
|
@@ -44,6 +44,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
|
|
44
44
|
private boolean mAutoDismiss;
|
|
45
45
|
private String mCategoryId;
|
|
46
46
|
private boolean mSticky;
|
|
47
|
+
private String mGroup;
|
|
47
48
|
|
|
48
49
|
protected NotificationContent() {
|
|
49
50
|
}
|
|
@@ -134,6 +135,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
|
|
134
135
|
return mCategoryId;
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
@Nullable
|
|
139
|
+
public String getGroup() {
|
|
140
|
+
return mGroup;
|
|
141
|
+
}
|
|
142
|
+
|
|
137
143
|
public boolean isSticky() {
|
|
138
144
|
return mSticky;
|
|
139
145
|
}
|
|
@@ -165,6 +171,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
|
|
165
171
|
mAutoDismiss = in.readByte() == 1;
|
|
166
172
|
mCategoryId = in.readString();
|
|
167
173
|
mSticky = in.readByte() == 1;
|
|
174
|
+
mGroup = in.readString();
|
|
168
175
|
}
|
|
169
176
|
|
|
170
177
|
@Override
|
|
@@ -183,6 +190,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
|
|
183
190
|
dest.writeByte((byte) (mAutoDismiss ? 1 : 0));
|
|
184
191
|
dest.writeString(mCategoryId);
|
|
185
192
|
dest.writeByte((byte) (mSticky ? 1 : 0));
|
|
193
|
+
dest.writeString(mGroup);
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
// EXPONOTIFCONTENT02
|
|
@@ -210,6 +218,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
|
|
210
218
|
out.writeByte(mAutoDismiss ? 1 : 0);
|
|
211
219
|
out.writeObject(mCategoryId != null ? mCategoryId.toString() : null);
|
|
212
220
|
out.writeByte(mSticky ? 1 : 0);
|
|
221
|
+
out.writeObject(mGroup);
|
|
213
222
|
}
|
|
214
223
|
|
|
215
224
|
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
|
|
@@ -257,6 +266,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
|
|
257
266
|
mCategoryId = new String(categoryIdString);
|
|
258
267
|
}
|
|
259
268
|
mSticky = in.readByte() == 1;
|
|
269
|
+
try {
|
|
270
|
+
mGroup = (String) in.readObject();
|
|
271
|
+
} catch (java.io.OptionalDataException | java.io.EOFException e) {
|
|
272
|
+
// Backward compatibility: old serialized data won't have this field
|
|
273
|
+
}
|
|
260
274
|
}
|
|
261
275
|
|
|
262
276
|
private void readObjectNoData() throws ObjectStreamException {
|
|
@@ -354,6 +368,12 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
|
|
354
368
|
return this;
|
|
355
369
|
}
|
|
356
370
|
|
|
371
|
+
public Builder setGroup(String group) {
|
|
372
|
+
// An empty group would be nameless. iOS also reports it as null.
|
|
373
|
+
content.mGroup = group == null || group.isEmpty() ? null : group;
|
|
374
|
+
return this;
|
|
375
|
+
}
|
|
376
|
+
|
|
357
377
|
public NotificationContent build() {
|
|
358
378
|
return content;
|
|
359
379
|
}
|
package/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
CHANGED
|
@@ -58,6 +58,9 @@ value class NotificationData(private val data: Map<String, String>) {
|
|
|
58
58
|
val categoryId: String?
|
|
59
59
|
get() = data["categoryId"]
|
|
60
60
|
|
|
61
|
+
val group: String?
|
|
62
|
+
get() = data["threadIdentifier"]?.takeIf { it.isNotEmpty() }
|
|
63
|
+
|
|
61
64
|
val subText: String?
|
|
62
65
|
get() = data["subtitle"]
|
|
63
66
|
|
|
@@ -67,6 +67,9 @@ class RemoteNotificationContent(private val remoteMessage: RemoteMessage) : INot
|
|
|
67
67
|
|
|
68
68
|
override val categoryId = notificationData.categoryId
|
|
69
69
|
|
|
70
|
+
// FCM's notification payload has no group field.
|
|
71
|
+
override val group = notificationData.group
|
|
72
|
+
|
|
70
73
|
override val isSticky = notificationData.isSticky
|
|
71
74
|
|
|
72
75
|
override val subText = notificationData.subText
|
|
@@ -114,6 +114,11 @@ open class ExpoNotificationBuilder(
|
|
|
114
114
|
|
|
115
115
|
applySoundsAndVibrations(content, builder)
|
|
116
116
|
|
|
117
|
+
content.group?.let {
|
|
118
|
+
builder.setGroup(it)
|
|
119
|
+
builder.setDeleteIntent(NotificationsService.createGroupedNotificationDeletedIntent(context, notification))
|
|
120
|
+
}
|
|
121
|
+
|
|
117
122
|
if (content.body != null) {
|
|
118
123
|
// Add body - JSON data - to extras
|
|
119
124
|
val extras = builder.extras
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package expo.modules.notifications.permissions
|
|
2
|
+
|
|
3
|
+
import expo.modules.interfaces.permissions.PermissionsResponse
|
|
4
|
+
import expo.modules.kotlin.records.Field
|
|
5
|
+
import expo.modules.kotlin.records.Record
|
|
6
|
+
import expo.modules.kotlin.types.OptimizedRecord
|
|
7
|
+
|
|
8
|
+
@OptimizedRecord
|
|
9
|
+
class AndroidNotificationPermissionDetails(
|
|
10
|
+
@Field
|
|
11
|
+
val importance: Int,
|
|
12
|
+
@Field
|
|
13
|
+
val interruptionFilter: Int
|
|
14
|
+
) : Record
|
|
15
|
+
|
|
16
|
+
@OptimizedRecord
|
|
17
|
+
class NotificationPermissionResponse(
|
|
18
|
+
@Field
|
|
19
|
+
val status: String,
|
|
20
|
+
@Field
|
|
21
|
+
val expires: String = PermissionsResponse.PERMISSION_EXPIRES_NEVER,
|
|
22
|
+
@Field
|
|
23
|
+
val canAskAgain: Boolean,
|
|
24
|
+
@Field
|
|
25
|
+
val granted: Boolean,
|
|
26
|
+
@Field
|
|
27
|
+
val android: AndroidNotificationPermissionDetails
|
|
28
|
+
) : Record
|
|
29
|
+
|
|
30
|
+
@OptimizedRecord
|
|
31
|
+
class ExactAlarmPermissionResponse(
|
|
32
|
+
@Field
|
|
33
|
+
val status: String,
|
|
34
|
+
@Field
|
|
35
|
+
val expires: String = PermissionsResponse.PERMISSION_EXPIRES_NEVER,
|
|
36
|
+
@Field
|
|
37
|
+
val canAskAgain: Boolean,
|
|
38
|
+
@Field
|
|
39
|
+
val granted: Boolean
|
|
40
|
+
) : Record
|
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
package expo.modules.notifications.permissions
|
|
2
2
|
|
|
3
3
|
import android.Manifest
|
|
4
|
-
import android.app.
|
|
4
|
+
import android.app.AlarmManager
|
|
5
5
|
import android.content.Context
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.content.pm.PackageManager
|
|
8
|
+
import android.net.Uri
|
|
6
9
|
import android.os.Build
|
|
10
|
+
import android.provider.Settings
|
|
7
11
|
import androidx.annotation.RequiresApi
|
|
8
12
|
import androidx.core.app.NotificationManagerCompat
|
|
9
|
-
import androidx.core.os.bundleOf
|
|
10
13
|
import expo.modules.core.arguments.ReadableArguments
|
|
11
14
|
import expo.modules.interfaces.permissions.Permissions
|
|
12
15
|
import expo.modules.interfaces.permissions.PermissionsResponse
|
|
13
16
|
import expo.modules.interfaces.permissions.PermissionsStatus
|
|
14
17
|
import expo.modules.kotlin.Promise
|
|
15
18
|
import expo.modules.kotlin.exception.Exceptions
|
|
19
|
+
import expo.modules.kotlin.functions.Queues
|
|
16
20
|
import expo.modules.kotlin.modules.Module
|
|
17
21
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
22
|
+
import expo.modules.notifications.ExactAlarmPermissionNotDeclaredException
|
|
18
23
|
import expo.modules.notifications.ModuleNotFoundException
|
|
19
24
|
|
|
20
|
-
private const val ANDROID_RESPONSE_KEY = "android"
|
|
21
|
-
private const val IMPORTANCE_KEY = "importance"
|
|
22
|
-
private const val INTERRUPTION_FILTER_KEY = "interruptionFilter"
|
|
23
25
|
private val PERMISSIONS: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
|
|
24
26
|
|
|
25
27
|
class NotificationPermissionsModule : Module() {
|
|
@@ -29,6 +31,8 @@ class NotificationPermissionsModule : Module() {
|
|
|
29
31
|
private val context: Context
|
|
30
32
|
get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
|
|
31
33
|
|
|
34
|
+
private val pendingExactAlarmPromises = mutableListOf<Promise>()
|
|
35
|
+
|
|
32
36
|
override fun definition() = ModuleDefinition {
|
|
33
37
|
Name("ExpoNotificationPermissionsModule")
|
|
34
38
|
|
|
@@ -47,6 +51,72 @@ class NotificationPermissionsModule : Module() {
|
|
|
47
51
|
getPermissionsWithPromiseImplClassic(promise)
|
|
48
52
|
}
|
|
49
53
|
}
|
|
54
|
+
|
|
55
|
+
AsyncFunction("getExactAlarmPermissionsAsync") {
|
|
56
|
+
getExactAlarmPermissionsResponse()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
AsyncFunction("requestExactAlarmPermissionsAsync") { promise: Promise ->
|
|
60
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || canScheduleExactAlarms()) {
|
|
61
|
+
promise.resolve(getExactAlarmPermissionsResponse())
|
|
62
|
+
return@AsyncFunction
|
|
63
|
+
}
|
|
64
|
+
if (!isScheduleExactAlarmDeclared()) {
|
|
65
|
+
throw ExactAlarmPermissionNotDeclaredException()
|
|
66
|
+
}
|
|
67
|
+
val activity = appContext.currentActivity ?: throw Exceptions.MissingActivity()
|
|
68
|
+
pendingExactAlarmPromises.add(promise)
|
|
69
|
+
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM, Uri.parse("package:${context.packageName}"))
|
|
70
|
+
try {
|
|
71
|
+
activity.startActivity(intent)
|
|
72
|
+
} catch (e: Throwable) {
|
|
73
|
+
pendingExactAlarmPromises.remove(promise)
|
|
74
|
+
throw e
|
|
75
|
+
}
|
|
76
|
+
}.runOnQueue(Queues.MAIN)
|
|
77
|
+
|
|
78
|
+
OnActivityEntersForeground {
|
|
79
|
+
if (pendingExactAlarmPromises.isNotEmpty()) {
|
|
80
|
+
val promises = pendingExactAlarmPromises.toList()
|
|
81
|
+
pendingExactAlarmPromises.clear()
|
|
82
|
+
val response = getExactAlarmPermissionsResponse()
|
|
83
|
+
promises.forEach { it.resolve(response) }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private fun canScheduleExactAlarms(): Boolean {
|
|
89
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
val alarmManager = context.getSystemService(AlarmManager::class.java)
|
|
93
|
+
return alarmManager?.canScheduleExactAlarms() ?: false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private fun isScheduleExactAlarmDeclared(): Boolean {
|
|
97
|
+
val requested = if (Build.VERSION.SDK_INT >= 33) {
|
|
98
|
+
context.packageManager
|
|
99
|
+
.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))
|
|
100
|
+
.requestedPermissions
|
|
101
|
+
} else {
|
|
102
|
+
@Suppress("DEPRECATION")
|
|
103
|
+
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS).requestedPermissions
|
|
104
|
+
}
|
|
105
|
+
return requested?.contains(Manifest.permission.SCHEDULE_EXACT_ALARM) == true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private fun getExactAlarmPermissionsResponse(): ExactAlarmPermissionResponse {
|
|
109
|
+
val granted = canScheduleExactAlarms()
|
|
110
|
+
val status = if (granted) {
|
|
111
|
+
PermissionsStatus.GRANTED
|
|
112
|
+
} else {
|
|
113
|
+
PermissionsStatus.DENIED
|
|
114
|
+
}
|
|
115
|
+
return ExactAlarmPermissionResponse(
|
|
116
|
+
status = status.status,
|
|
117
|
+
canAskAgain = granted || isScheduleExactAlarmDeclared(),
|
|
118
|
+
granted = granted
|
|
119
|
+
)
|
|
50
120
|
}
|
|
51
121
|
|
|
52
122
|
@RequiresApi(33)
|
|
@@ -55,15 +125,6 @@ class NotificationPermissionsModule : Module() {
|
|
|
55
125
|
{ permissionsMap: Map<String, PermissionsResponse> ->
|
|
56
126
|
val managerCompat = NotificationManagerCompat.from(context)
|
|
57
127
|
val areEnabled = managerCompat.areNotificationsEnabled()
|
|
58
|
-
val platformBundle = bundleOf(
|
|
59
|
-
IMPORTANCE_KEY to managerCompat.importance
|
|
60
|
-
).apply {
|
|
61
|
-
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
|
|
62
|
-
if (notificationManager != null) {
|
|
63
|
-
putInt(INTERRUPTION_FILTER_KEY, notificationManager.currentInterruptionFilter)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
128
|
val areAllGranted = permissionsMap.all { (_, response) -> response.status == PermissionsStatus.GRANTED }
|
|
68
129
|
val areAllDenied = permissionsMap.all { (_, response) -> response.status == PermissionsStatus.DENIED }
|
|
69
130
|
val canAskAgain = permissionsMap.all { (_, response) -> response.canAskAgain }
|
|
@@ -75,12 +136,11 @@ class NotificationPermissionsModule : Module() {
|
|
|
75
136
|
}
|
|
76
137
|
|
|
77
138
|
promise.resolve(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
ANDROID_RESPONSE_KEY to platformBundle
|
|
139
|
+
NotificationPermissionResponse(
|
|
140
|
+
status = status,
|
|
141
|
+
canAskAgain = canAskAgain,
|
|
142
|
+
granted = areAllGranted,
|
|
143
|
+
android = getAndroidDetails(managerCompat)
|
|
84
144
|
)
|
|
85
145
|
)
|
|
86
146
|
},
|
|
@@ -96,26 +156,23 @@ class NotificationPermissionsModule : Module() {
|
|
|
96
156
|
} else {
|
|
97
157
|
PermissionsStatus.DENIED
|
|
98
158
|
}
|
|
99
|
-
val platformBundle = bundleOf(
|
|
100
|
-
IMPORTANCE_KEY to managerCompat.importance
|
|
101
|
-
).apply {
|
|
102
|
-
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
|
|
103
|
-
if (notificationManager != null) {
|
|
104
|
-
putInt(INTERRUPTION_FILTER_KEY, notificationManager.currentInterruptionFilter)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
159
|
|
|
108
160
|
promise.resolve(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
ANDROID_RESPONSE_KEY to platformBundle
|
|
161
|
+
NotificationPermissionResponse(
|
|
162
|
+
status = status.status,
|
|
163
|
+
canAskAgain = areEnabled,
|
|
164
|
+
granted = status == PermissionsStatus.GRANTED,
|
|
165
|
+
android = getAndroidDetails(managerCompat)
|
|
115
166
|
)
|
|
116
167
|
)
|
|
117
168
|
}
|
|
118
169
|
|
|
170
|
+
private fun getAndroidDetails(managerCompat: NotificationManagerCompat) =
|
|
171
|
+
AndroidNotificationPermissionDetails(
|
|
172
|
+
importance = managerCompat.importance,
|
|
173
|
+
interruptionFilter = managerCompat.currentInterruptionFilter
|
|
174
|
+
)
|
|
175
|
+
|
|
119
176
|
@RequiresApi(33)
|
|
120
177
|
private fun requestPermissionsWithPromiseImplApi33(promise: Promise) {
|
|
121
178
|
permissions.askForPermissions(
|
|
@@ -56,6 +56,7 @@ open class NotificationsService : BroadcastReceiver() {
|
|
|
56
56
|
private const val GET_SCHEDULED_TYPE = "getScheduled"
|
|
57
57
|
private const val REMOVE_SELECTED_TYPE = "removeSelected"
|
|
58
58
|
private const val REMOVE_ALL_TYPE = "removeAll"
|
|
59
|
+
private const val GROUPED_NOTIFICATION_DELETED_TYPE = "groupedNotificationDeleted"
|
|
59
60
|
|
|
60
61
|
// Messages parts
|
|
61
62
|
const val SUCCESS_CODE = 0
|
|
@@ -443,6 +444,28 @@ open class NotificationsService : BroadcastReceiver() {
|
|
|
443
444
|
)
|
|
444
445
|
}
|
|
445
446
|
|
|
447
|
+
fun createGroupedNotificationDeletedIntent(context: Context, notification: Notification): PendingIntent {
|
|
448
|
+
// The identifier in the URI keeps FLAG_UPDATE_CURRENT from replacing one child's extras with another's.
|
|
449
|
+
val intent = Intent(
|
|
450
|
+
NOTIFICATION_EVENT_ACTION,
|
|
451
|
+
getUriBuilderForIdentifier(notification.notificationRequest.identifier).appendPath("groupedDeleted").build()
|
|
452
|
+
).also { intent ->
|
|
453
|
+
findDesignatedBroadcastReceiver(context, intent)?.let {
|
|
454
|
+
intent.component = ComponentName(it.packageName, it.name)
|
|
455
|
+
}
|
|
456
|
+
intent.putExtra(EVENT_TYPE_KEY, GROUPED_NOTIFICATION_DELETED_TYPE)
|
|
457
|
+
intent.putExtra(NOTIFICATION_KEY, notification)
|
|
458
|
+
// Byte-array copy: Parcelable extras can come back null from a PendingIntent, see #38908
|
|
459
|
+
marshalObject(notification)?.let { intent.putExtra(NOTIFICATION_BYTES_KEY, it) }
|
|
460
|
+
}
|
|
461
|
+
return PendingIntent.getBroadcast(
|
|
462
|
+
context,
|
|
463
|
+
0,
|
|
464
|
+
intent,
|
|
465
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
446
469
|
/**
|
|
447
470
|
* Creates and returns a pending intent that will trigger [NotificationsService]'s "response received"
|
|
448
471
|
* event.
|
|
@@ -694,6 +717,8 @@ open class NotificationsService : BroadcastReceiver() {
|
|
|
694
717
|
|
|
695
718
|
TRIGGER_TYPE -> onNotificationTriggered(context, intent)
|
|
696
719
|
|
|
720
|
+
GROUPED_NOTIFICATION_DELETED_TYPE -> onGroupedNotificationDeleted(context, intent)
|
|
721
|
+
|
|
697
722
|
else -> throw IllegalArgumentException("Received event of unrecognized type: $eventType. Ignoring.")
|
|
698
723
|
}
|
|
699
724
|
|
|
@@ -753,11 +778,24 @@ open class NotificationsService : BroadcastReceiver() {
|
|
|
753
778
|
open fun onReceiveNotificationResponse(context: Context, intent: Intent) {
|
|
754
779
|
val response = getNotificationResponseFromBroadcastIntent(intent)
|
|
755
780
|
getHandlingDelegate(context).handleNotificationResponse(response)
|
|
781
|
+
// Auto-cancel on tap does not fire the delete intent. Action buttons do not dismiss.
|
|
782
|
+
val content = response.notification.notificationRequest.content
|
|
783
|
+
val isTap = response.actionIdentifier == NotificationResponse.DEFAULT_ACTION_IDENTIFIER
|
|
784
|
+
if (isTap && content.isAutoDismiss && content.group != null) {
|
|
785
|
+
getPresentationDelegate(context).removeOrphanedGroupSummaries(response.notification)
|
|
786
|
+
}
|
|
756
787
|
}
|
|
757
788
|
|
|
758
789
|
open fun onNotificationsDropped(context: Context, intent: Intent) =
|
|
759
790
|
getHandlingDelegate(context).handleNotificationsDropped()
|
|
760
791
|
|
|
792
|
+
open fun onGroupedNotificationDeleted(context: Context, intent: Intent) {
|
|
793
|
+
val notification = intent.getParcelableExtra<Notification>(NOTIFICATION_KEY)
|
|
794
|
+
?: unmarshalObject(Notification.CREATOR, intent.getByteArrayExtra(NOTIFICATION_BYTES_KEY))
|
|
795
|
+
?: throw IllegalArgumentException("$NOTIFICATION_KEY not found in the intent extras.")
|
|
796
|
+
getPresentationDelegate(context).removeOrphanedGroupSummaries(notification)
|
|
797
|
+
}
|
|
798
|
+
|
|
761
799
|
//endregion
|
|
762
800
|
|
|
763
801
|
//region Category handling
|