react-native-notify-kit 10.4.9 → 10.6.0
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/README.md +60 -2
- package/android/src/main/java/app/notifee/core/Notifee.java +9 -0
- package/android/src/main/java/app/notifee/core/ReceiverService.java +41 -4
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidStyleModel.java +63 -34
- package/android/src/main/java/app/notifee/core/utility/FullScreenIntentUtils.java +53 -0
- package/android/src/main/java/app/notifee/core/utility/ResourceUtils.java +15 -1
- package/android/src/test/java/app/notifee/core/ReceiverServicePendingIntentIdentityTest.java +303 -0
- package/android/src/test/java/app/notifee/core/model/NotificationAndroidStyleModelPersonFailureTest.java +337 -0
- package/android/src/test/java/app/notifee/core/model/TimestampTriggerModelTest.java +27 -70
- package/android/src/test/java/app/notifee/core/utility/FullScreenIntentUtilsTest.java +83 -0
- package/android/src/test/java/app/notifee/core/utility/ResourceUtilsBitmapOwnershipTest.java +145 -0
- package/dist/NotifeeApiModule.js +4 -0
- package/dist/NotifeeApiModule.js.map +1 -1
- package/dist/types/Notification.d.ts +2 -1
- package/dist/types/Notification.js.map +1 -1
- package/dist/types/NotificationAndroid.d.ts +17 -5
- package/dist/types/NotificationAndroid.js.map +1 -1
- package/dist/validators/validateAndroidNotification.js +4 -0
- package/dist/validators/validateAndroidNotification.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/NotifeeCore/NotifeeCoreExtensionHelper.m +285 -91
- package/ios/NotifeeCore/NotifeeCoreUtil.m +159 -13
- package/jest-mock.js +1 -0
- package/package.json +2 -2
- package/server/README.md +6 -4
- package/server/dist/android.d.ts +8 -0
- package/server/dist/android.d.ts.map +1 -0
- package/server/dist/android.js +20 -0
- package/server/dist/android.js.map +1 -0
- package/server/dist/buildPayload.d.ts +3 -0
- package/server/dist/buildPayload.d.ts.map +1 -0
- package/server/dist/buildPayload.js +82 -0
- package/server/dist/buildPayload.js.map +1 -0
- package/server/dist/index.d.ts +6 -0
- package/server/dist/index.d.ts.map +1 -0
- package/server/dist/index.js +12 -0
- package/server/dist/index.js.map +1 -0
- package/server/dist/ios.d.ts +11 -0
- package/server/dist/ios.d.ts.map +1 -0
- package/server/dist/ios.js +61 -0
- package/server/dist/ios.js.map +1 -0
- package/server/dist/serialize.d.ts +11 -0
- package/server/dist/serialize.d.ts.map +1 -0
- package/server/dist/serialize.js +32 -0
- package/server/dist/serialize.js.map +1 -0
- package/server/dist/types.d.ts +2 -0
- package/server/dist/types.d.ts.map +1 -0
- package/server/dist/types.js +3 -0
- package/server/dist/types.js.map +1 -0
- package/server/dist/validation.d.ts +3 -0
- package/server/dist/validation.d.ts.map +1 -0
- package/server/dist/validation.js +115 -0
- package/server/dist/validation.js.map +1 -0
- package/server/src/android.ts +4 -4
- package/server/src/validation.ts +20 -0
- package/src/NotifeeApiModule.ts +4 -0
- package/src/internal/fcmContract.d.ts +10 -8
- package/src/types/Notification.ts +2 -1
- package/src/types/NotificationAndroid.ts +18 -5
- package/src/validators/validateAndroidNotification.ts +7 -0
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -148,6 +148,8 @@ useEffect(() => {
|
|
|
148
148
|
|
|
149
149
|
> **Android event reliability:** Registering `onForegroundEvent()` initializes the native event relay. Pending events received while the `ReactContext` is unavailable are buffered. If the context becomes unavailable after a flush takes its buffer snapshot, undelivered events are requeued so a later native event relay flush can deliver them without duplication in the validated recovery path, while preserving FIFO, the 10-event capacity, and drop-oldest behavior. No application code changes or warm-up calls such as `getDisplayedNotifications()` are required; continue registering foreground and background handlers at the recommended points shown above. This hardens the Android event path investigated in [react-native-notify-kit#47](https://github.com/marcocrupi/react-native-notify-kit/issues/47) and relates to the event reliability and buffering problem tracked in [invertase/notifee#1279](https://github.com/invertase/notifee/issues/1279); the scenarios are not necessarily identical.
|
|
150
150
|
|
|
151
|
+
<!-- Keep these callouts separate. -->
|
|
152
|
+
|
|
151
153
|
> **Which handler fires when (iOS):**
|
|
152
154
|
>
|
|
153
155
|
> - Tap a notification while the app is **active in foreground** → `onForegroundEvent` receives `PRESS`.
|
|
@@ -156,6 +158,42 @@ useEffect(() => {
|
|
|
156
158
|
>
|
|
157
159
|
> Register **both** handlers if you need to react to taps in every app state. Resolves the confusion reported in upstream [invertase/notifee#1155](https://github.com/invertase/notifee/issues/1155).
|
|
158
160
|
|
|
161
|
+
### Android notification numbers and launcher badges
|
|
162
|
+
|
|
163
|
+
For notifications displayed through `notifee.displayNotification()`, use `android.badgeCount` to associate a number with
|
|
164
|
+
an individual Android notification:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
await notifee.createChannel({
|
|
168
|
+
id: 'messages',
|
|
169
|
+
name: 'Messages',
|
|
170
|
+
badge: true,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await notifee.displayNotification({
|
|
174
|
+
id: 'messages',
|
|
175
|
+
title: 'New messages',
|
|
176
|
+
body: 'You have 5 unread messages',
|
|
177
|
+
android: {
|
|
178
|
+
channelId: 'messages',
|
|
179
|
+
badgeCount: 5,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`android.badgeCount` belongs to that notification and maps conceptually to AndroidX
|
|
185
|
+
`NotificationCompat.Builder.setNumber(...)`. Display the notification again with the same `id` and a new `badgeCount`
|
|
186
|
+
to update or replace it. The notification number itself is not limited to API level 26.
|
|
187
|
+
|
|
188
|
+
On Android 8.0 (API level 26) and above, `badge: true` allows notifications in the channel to contribute to launcher
|
|
189
|
+
badging; it does not set a count. Launcher and OEM behavior determines whether a number, only a notification dot, or a
|
|
190
|
+
different aggregation is shown, so a numeric badge on the app icon is not guaranteed.
|
|
191
|
+
|
|
192
|
+
Unlike the global iOS `notifee.setBadgeCount()` API, Android has no equivalent standard public API independent of posted
|
|
193
|
+
notifications. `badgeCount: 0` neither cancels the posted notification nor provides a portable way to clear a global
|
|
194
|
+
Android badge. See [Android Appearance: Badges](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/appearance#badges)
|
|
195
|
+
for complete details.
|
|
196
|
+
|
|
159
197
|
## Notifee FCM Mode (NEW in 10.0.0, Expo CNG in 10.4.0)
|
|
160
198
|
|
|
161
199
|
**Use `react-native-notify-kit` as the sole FCM display layer on both Android and iOS**: one developer API, no duplicate notifications on Android, no silent-push drops on iOS. Ship a server SDK payload, let the client handle it in one line, and set up the iOS Notification Service Extension with the Expo config plugin or, for bare React Native, the `init-nse` CLI. On Android, FCM Mode remains data-only: RNFirebase receives the message and calls `notifee.handleFcmMessage`.
|
|
@@ -377,7 +415,7 @@ For bare React Native projects with non-standard iOS paths or heavily-customized
|
|
|
377
415
|
|
|
378
416
|
## Server SDK
|
|
379
417
|
|
|
380
|
-
`react-native-notify-kit` ships a **zero-dependency server SDK** under the `/server` subpath
|
|
418
|
+
`react-native-notify-kit` ships a **zero-dependency server SDK** under the `/server` subpath for building Firebase Admin-compatible FCM messages that the client `handleFcmMessage` handler consumes. Firebase Admin serializes each message to the FCM HTTP v1 request format. The SDK runs in Node.js 22+ and Firebase Cloud Functions.
|
|
381
419
|
|
|
382
420
|
```ts
|
|
383
421
|
import { buildNotifyKitPayload } from 'react-native-notify-kit/server';
|
|
@@ -399,6 +437,8 @@ const message = buildNotifyKitPayload({
|
|
|
399
437
|
await admin.messaging().send(message);
|
|
400
438
|
```
|
|
401
439
|
|
|
440
|
+
`options.ttl` is expressed in seconds. Pass the message directly to Firebase Admin without an adapter or a manual unit conversion.
|
|
441
|
+
|
|
402
442
|
Full reference: [docs/fcm-mode.mdx#server-sdk-reference](docs/fcm-mode.mdx#server-sdk-reference) (types, validation rules, payload shape, FCM 4 KB limit).
|
|
403
443
|
|
|
404
444
|
## CLI Tools
|
|
@@ -501,7 +541,7 @@ This fork fixes the following bugs that were never resolved in the original Noti
|
|
|
501
541
|
>
|
|
502
542
|
> **Reserved keys filtered from `data`** (custom payload keys matching these are dropped): prefixes `android.`, `google.`, `gcm.`, `fcm.` (with trailing dot — `fcmRegion`, `googleish` survive), `notifee` (no trailing dot — library's reserved namespace, `notifeeFoo` is also filtered), plus exact keys `from`, `collapse_key`, `message_type`, `message_id`, `aps`, `fcm_options`. The `fcm_options` exact-match matches iOS behavior on the Firebase analytics-label key. **Cross-platform note**: bare-`fcm` keys other than `fcm_options` (e.g. `fcmRegion`, `fcmToken`) are preserved on Android but filtered on iOS — rename server-side if you need strict parity.
|
|
503
543
|
|
|
504
|
-
<!--
|
|
544
|
+
<!-- Keep these callouts separate. -->
|
|
505
545
|
|
|
506
546
|
> **Note for policy-eligible apps requiring exact alarm APIs (alarm clocks, timers, calendars):**
|
|
507
547
|
> Add `<uses-permission android:name="android.permission.USE_EXACT_ALARM" />` to your app's
|
|
@@ -798,6 +838,24 @@ await notifee.displayNotification({
|
|
|
798
838
|
|
|
799
839
|
## New APIs
|
|
800
840
|
|
|
841
|
+
### Full-Screen Intent availability (Android)
|
|
842
|
+
|
|
843
|
+
Use `getNotificationSettings()` to check whether the app currently has access to use Full-Screen Intents:
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
import notifee, { AndroidNotificationSetting } from 'react-native-notify-kit';
|
|
847
|
+
|
|
848
|
+
const settings = await notifee.getNotificationSettings();
|
|
849
|
+
|
|
850
|
+
if (settings.android.fullScreenIntent === AndroidNotificationSetting.DISABLED) {
|
|
851
|
+
// Full-Screen Intent access is currently unavailable.
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
`AndroidNotificationSetting.ENABLED` reports Full-Screen Intent access; it does not guarantee that a particular
|
|
856
|
+
notification will be presented full-screen. See [Android Behaviour: Full-screen](https://docs.page/marcocrupi/react-native-notify-kit/react-native/android/behaviour#full-screen)
|
|
857
|
+
for the full requirements and presentation behavior.
|
|
858
|
+
|
|
801
859
|
### `setNotificationConfig` (iOS)
|
|
802
860
|
|
|
803
861
|
Controls whether Notifee intercepts remote (push) notification tap events on iOS.
|
|
@@ -36,6 +36,7 @@ import app.notifee.core.model.ChannelGroupModel;
|
|
|
36
36
|
import app.notifee.core.model.ChannelModel;
|
|
37
37
|
import app.notifee.core.model.NotificationModel;
|
|
38
38
|
import app.notifee.core.utility.AlarmUtils;
|
|
39
|
+
import app.notifee.core.utility.FullScreenIntentUtils;
|
|
39
40
|
import app.notifee.core.utility.PowerManagerUtils;
|
|
40
41
|
import com.google.common.util.concurrent.FutureCallback;
|
|
41
42
|
import com.google.common.util.concurrent.Futures;
|
|
@@ -508,6 +509,14 @@ public class Notifee {
|
|
|
508
509
|
androidSettingsBundle.putInt("alarm", 0);
|
|
509
510
|
}
|
|
510
511
|
|
|
512
|
+
boolean canUseFullScreenIntent = FullScreenIntentUtils.canUseFullScreenIntent();
|
|
513
|
+
|
|
514
|
+
if (canUseFullScreenIntent) {
|
|
515
|
+
androidSettingsBundle.putInt("fullScreenIntent", 1);
|
|
516
|
+
} else {
|
|
517
|
+
androidSettingsBundle.putInt("fullScreenIntent", 0);
|
|
518
|
+
}
|
|
519
|
+
|
|
511
520
|
notificationSettingsBundle.putBundle("android", androidSettingsBundle);
|
|
512
521
|
result.onComplete(null, notificationSettingsBundle);
|
|
513
522
|
}
|
|
@@ -26,6 +26,7 @@ import android.app.PendingIntent;
|
|
|
26
26
|
import android.app.Service;
|
|
27
27
|
import android.content.Context;
|
|
28
28
|
import android.content.Intent;
|
|
29
|
+
import android.net.Uri;
|
|
29
30
|
import android.os.Build;
|
|
30
31
|
import android.os.Bundle;
|
|
31
32
|
import android.os.IBinder;
|
|
@@ -39,14 +40,16 @@ import app.notifee.core.model.NotificationAndroidModel;
|
|
|
39
40
|
import app.notifee.core.model.NotificationAndroidPressActionModel;
|
|
40
41
|
import app.notifee.core.model.NotificationModel;
|
|
41
42
|
import app.notifee.core.utility.IntentUtils;
|
|
42
|
-
import java.util.concurrent.atomic.AtomicInteger;
|
|
43
43
|
|
|
44
44
|
public class ReceiverService extends Service {
|
|
45
45
|
private static final String TAG = "ReceiverService";
|
|
46
46
|
public static final String REMOTE_INPUT_RECEIVER_KEY =
|
|
47
47
|
"app.notifee.core.ReceiverService.REMOTE_INPUT_RECEIVER_KEY";
|
|
48
48
|
|
|
49
|
-
private static final
|
|
49
|
+
private static final int PENDING_INTENT_REQUEST_CODE = 0;
|
|
50
|
+
private static final String PENDING_INTENT_URI_SCHEME = "notifee";
|
|
51
|
+
private static final String PENDING_INTENT_URI_AUTHORITY = "receiver-service";
|
|
52
|
+
private static final String PENDING_INTENT_URI_PATH = "pending-intent";
|
|
50
53
|
|
|
51
54
|
static final String DELETE_INTENT = "app.notifee.core.ReceiverService.DELETE_INTENT";
|
|
52
55
|
static final String PRESS_INTENT = "app.notifee.core.ReceiverService.PRESS_INTENT";
|
|
@@ -76,9 +79,43 @@ public class ReceiverService extends Service {
|
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
intent.setData(createPendingIntentData(intent));
|
|
83
|
+
|
|
80
84
|
return PendingIntent.getService(
|
|
81
|
-
context,
|
|
85
|
+
context,
|
|
86
|
+
PENDING_INTENT_REQUEST_CODE,
|
|
87
|
+
intent,
|
|
88
|
+
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private static Uri createPendingIntentData(Intent intent) {
|
|
92
|
+
Bundle notification = intent.getBundleExtra("notification");
|
|
93
|
+
Bundle android = notification == null ? null : notification.getBundle("android");
|
|
94
|
+
Bundle pressAction = intent.getBundleExtra("pressAction");
|
|
95
|
+
|
|
96
|
+
String notificationId = notification == null ? null : notification.getString("id");
|
|
97
|
+
String notificationTag = android == null ? null : android.getString("tag");
|
|
98
|
+
String pressActionId = pressAction == null ? null : pressAction.getString("id");
|
|
99
|
+
|
|
100
|
+
Uri.Builder builder =
|
|
101
|
+
new Uri.Builder()
|
|
102
|
+
.scheme(PENDING_INTENT_URI_SCHEME)
|
|
103
|
+
.authority(PENDING_INTENT_URI_AUTHORITY)
|
|
104
|
+
.appendPath(PENDING_INTENT_URI_PATH);
|
|
105
|
+
|
|
106
|
+
appendNullableIdentityValue(builder, "interactionType", intent.getAction());
|
|
107
|
+
appendNullableIdentityValue(builder, "notificationTag", notificationTag);
|
|
108
|
+
appendNullableIdentityValue(builder, "notificationId", notificationId);
|
|
109
|
+
appendNullableIdentityValue(builder, "pressActionId", pressActionId);
|
|
110
|
+
return builder.build();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static void appendNullableIdentityValue(
|
|
114
|
+
Uri.Builder builder, String name, @Nullable String value) {
|
|
115
|
+
builder.appendQueryParameter(name + "Present", value == null ? "0" : "1");
|
|
116
|
+
if (value != null) {
|
|
117
|
+
builder.appendQueryParameter(name, value);
|
|
118
|
+
}
|
|
82
119
|
}
|
|
83
120
|
|
|
84
121
|
@Nullable
|
|
@@ -34,6 +34,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
|
|
34
34
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
35
35
|
import java.util.ArrayList;
|
|
36
36
|
import java.util.Objects;
|
|
37
|
+
import java.util.concurrent.ExecutionException;
|
|
37
38
|
import java.util.concurrent.TimeUnit;
|
|
38
39
|
import java.util.concurrent.TimeoutException;
|
|
39
40
|
|
|
@@ -50,6 +51,31 @@ public class NotificationAndroidStyleModel {
|
|
|
50
51
|
return new NotificationAndroidStyleModel(styleBundle);
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/** Builds a person from everything in its bundle except the remotely fetched icon. */
|
|
55
|
+
private static Person.Builder getPersonBuilder(Bundle personBundle) {
|
|
56
|
+
Person.Builder personBuilder = new Person.Builder();
|
|
57
|
+
|
|
58
|
+
personBuilder.setName(personBundle.getString("name"));
|
|
59
|
+
|
|
60
|
+
if (personBundle.containsKey("id")) {
|
|
61
|
+
personBuilder.setKey(personBundle.getString("id"));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (personBundle.containsKey("bot")) {
|
|
65
|
+
personBuilder.setBot(personBundle.getBoolean("bot"));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (personBundle.containsKey("important")) {
|
|
69
|
+
personBuilder.setImportant(personBundle.getBoolean("important"));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (personBundle.containsKey("uri")) {
|
|
73
|
+
personBuilder.setUri(personBundle.getString("uri"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return personBuilder;
|
|
77
|
+
}
|
|
78
|
+
|
|
53
79
|
/**
|
|
54
80
|
* Converts a person bundle from JS into a Person
|
|
55
81
|
*
|
|
@@ -60,54 +86,59 @@ public class NotificationAndroidStyleModel {
|
|
|
60
86
|
ListeningExecutorService lExecutor, Bundle personBundle) {
|
|
61
87
|
return lExecutor.submit(
|
|
62
88
|
() -> {
|
|
63
|
-
Person.Builder personBuilder =
|
|
64
|
-
|
|
65
|
-
personBuilder.setName(personBundle.getString("name"));
|
|
66
|
-
|
|
67
|
-
if (personBundle.containsKey("id")) {
|
|
68
|
-
personBuilder.setKey(personBundle.getString("id"));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (personBundle.containsKey("bot")) {
|
|
72
|
-
personBuilder.setBot(personBundle.getBoolean("bot"));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (personBundle.containsKey("important")) {
|
|
76
|
-
personBuilder.setImportant(personBundle.getBoolean("important"));
|
|
77
|
-
}
|
|
89
|
+
Person.Builder personBuilder = getPersonBuilder(personBundle);
|
|
78
90
|
|
|
79
91
|
if (personBundle.containsKey("icon")) {
|
|
80
92
|
String personIcon = Objects.requireNonNull(personBundle.getString("icon"));
|
|
81
|
-
Bitmap personIconBitmap = null;
|
|
82
93
|
|
|
83
94
|
try {
|
|
84
|
-
personIconBitmap =
|
|
95
|
+
Bitmap personIconBitmap =
|
|
85
96
|
ResourceUtils.getImageBitmapFromUrl(personIcon).get(10, TimeUnit.SECONDS);
|
|
97
|
+
|
|
98
|
+
if (personIconBitmap != null) {
|
|
99
|
+
personBuilder.setIcon(IconCompat.createWithAdaptiveBitmap(personIconBitmap));
|
|
100
|
+
}
|
|
86
101
|
} catch (TimeoutException e) {
|
|
87
102
|
Logger.e(
|
|
88
103
|
TAG,
|
|
89
104
|
"Timeout occurred whilst trying to retrieve a person icon: " + personIcon,
|
|
90
105
|
e);
|
|
91
|
-
} catch (
|
|
106
|
+
} catch (ExecutionException | RuntimeException e) {
|
|
92
107
|
Logger.e(
|
|
93
108
|
TAG,
|
|
94
|
-
"An error occurred whilst trying to retrieve a person icon: "
|
|
109
|
+
"An error occurred whilst trying to retrieve or create a person icon: "
|
|
110
|
+
+ personIcon,
|
|
95
111
|
e);
|
|
96
112
|
}
|
|
97
|
-
|
|
98
|
-
if (personIconBitmap != null) {
|
|
99
|
-
personBuilder.setIcon(IconCompat.createWithAdaptiveBitmap(personIconBitmap));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (personBundle.containsKey("uri")) {
|
|
104
|
-
personBuilder.setUri(personBundle.getString("uri"));
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
return personBuilder.build();
|
|
108
116
|
});
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Awaits a person, degrading to an icon-less one only if the outer deadline expires.
|
|
121
|
+
*
|
|
122
|
+
* <p>The icon lookup has a shorter timeout, but scheduling delays, process suspension, or other
|
|
123
|
+
* task stalls can still exhaust this outer deadline. The runtime cause is not inferred here.
|
|
124
|
+
*
|
|
125
|
+
* <p>ExecutionException and InterruptedException propagate because neither proves that only the
|
|
126
|
+
* icon failed.
|
|
127
|
+
*/
|
|
128
|
+
private static Person awaitPerson(ListenableFuture<Person> personTask, Bundle personBundle)
|
|
129
|
+
throws InterruptedException, ExecutionException {
|
|
130
|
+
try {
|
|
131
|
+
return personTask.get(20, TimeUnit.SECONDS);
|
|
132
|
+
} catch (TimeoutException e) {
|
|
133
|
+
Logger.e(TAG, "Timeout occurred whilst trying to retrieve a messaging style person", e);
|
|
134
|
+
} catch (ExecutionException e) {
|
|
135
|
+
Logger.e(TAG, "Unexpected failure whilst building a messaging style person", e);
|
|
136
|
+
throw e;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return getPersonBuilder(personBundle).build();
|
|
140
|
+
}
|
|
141
|
+
|
|
111
142
|
public Bundle toBundle() {
|
|
112
143
|
return (Bundle) mNotificationAndroidStyleBundle.clone();
|
|
113
144
|
}
|
|
@@ -291,11 +322,9 @@ public class NotificationAndroidStyleModel {
|
|
|
291
322
|
ListeningExecutorService lExecutor) {
|
|
292
323
|
return lExecutor.submit(
|
|
293
324
|
() -> {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
Objects.requireNonNull(mNotificationAndroidStyleBundle.getBundle("person")))
|
|
298
|
-
.get(20, TimeUnit.SECONDS);
|
|
325
|
+
Bundle personBundle =
|
|
326
|
+
Objects.requireNonNull(mNotificationAndroidStyleBundle.getBundle("person"));
|
|
327
|
+
Person person = awaitPerson(getPerson(lExecutor, personBundle), personBundle);
|
|
299
328
|
|
|
300
329
|
NotificationCompat.MessagingStyle messagingStyle =
|
|
301
330
|
new NotificationCompat.MessagingStyle(person);
|
|
@@ -322,9 +351,9 @@ public class NotificationAndroidStyleModel {
|
|
|
322
351
|
long timestamp = BundleValueReader.getLongPreserving(message, "timestamp");
|
|
323
352
|
|
|
324
353
|
if (message.containsKey("person")) {
|
|
354
|
+
Bundle messagePersonBundle = Objects.requireNonNull(message.getBundle("person"));
|
|
325
355
|
messagePerson =
|
|
326
|
-
getPerson(lExecutor,
|
|
327
|
-
.get(20, TimeUnit.SECONDS);
|
|
356
|
+
awaitPerson(getPerson(lExecutor, messagePersonBundle), messagePersonBundle);
|
|
328
357
|
}
|
|
329
358
|
|
|
330
359
|
messagingStyle =
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package app.notifee.core.utility;
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this library except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import static app.notifee.core.ContextHolder.getApplicationContext;
|
|
21
|
+
|
|
22
|
+
import android.content.Context;
|
|
23
|
+
import androidx.annotation.Nullable;
|
|
24
|
+
import androidx.annotation.VisibleForTesting;
|
|
25
|
+
import androidx.core.app.NotificationManagerCompat;
|
|
26
|
+
|
|
27
|
+
public class FullScreenIntentUtils {
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether the app currently has the Android access required to use full-screen intents.
|
|
31
|
+
*
|
|
32
|
+
* <p>API levels below 29 do not use the full-screen intent permission model. API levels 29
|
|
33
|
+
* through 33 require {@code USE_FULL_SCREEN_INTENT}. API level 34 and above use Android's
|
|
34
|
+
* full-screen intent special app access.
|
|
35
|
+
*
|
|
36
|
+
* @return true when the required access is currently available. This does not guarantee that a
|
|
37
|
+
* particular notification will be presented full-screen.
|
|
38
|
+
*/
|
|
39
|
+
public static boolean canUseFullScreenIntent() {
|
|
40
|
+
return canUseFullScreenIntent(getApplicationContext());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@VisibleForTesting
|
|
44
|
+
static boolean canUseFullScreenIntent(@Nullable Context context) {
|
|
45
|
+
// The application context is expected to exist on the public path. Preserve the existing
|
|
46
|
+
// defensive fail-open behavior for an internal state that cannot prove access is denied.
|
|
47
|
+
if (context == null) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return NotificationManagerCompat.from(context).canUseFullScreenIntent();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -170,7 +170,21 @@ public class ResourceUtils {
|
|
|
170
170
|
new BaseBitmapDataSubscriber() {
|
|
171
171
|
@Override
|
|
172
172
|
protected void onNewResultImpl(@Nullable Bitmap bitmap) {
|
|
173
|
-
|
|
173
|
+
// Fresco owns this pooled bitmap and only guarantees it is valid for the
|
|
174
|
+
// duration of this callback. If it escapes without a copy, Fresco may
|
|
175
|
+
// recycle it on memory-cache eviction (e.g. onTrimMemory) while the
|
|
176
|
+
// notification is still being built, posted or re-parceled, crashing with
|
|
177
|
+
// "Can't copy/parcel a recycled bitmap" or "Canvas: trying to use a
|
|
178
|
+
// recycled bitmap".
|
|
179
|
+
if (bitmap == null) {
|
|
180
|
+
bitmapTCS.set(null);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
Bitmap.Config config = bitmap.getConfig();
|
|
184
|
+
if (config == null) {
|
|
185
|
+
config = Bitmap.Config.ARGB_8888;
|
|
186
|
+
}
|
|
187
|
+
bitmapTCS.set(bitmap.copy(config, false));
|
|
174
188
|
}
|
|
175
189
|
|
|
176
190
|
@Override
|