Skip to content

Commit 5404ab3

Browse files
committed
Badge now uses notification count from system
* Badge count is now always set to based on the notifications in the shade, regardless if they are from OneSignal or not * Fixing issue where new notifications would not show if there are already 49 notifications in the shade - Now handles removing oldest notifications to make room for new ones. * Clearing notifications now works even if the privacy was not accepted. * Fixed issue where badges did not clear if clear notifications was called before init. * Badge counts are now updated after restoring notifications.
1 parent dfa57ea commit 5404ab3

File tree

9 files changed

+320
-36
lines changed

9 files changed

+320
-36
lines changed

OneSignalSDK/onesignal/src/main/java/com/onesignal/BadgeCountUpdater.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,22 @@
2727

2828
package com.onesignal;
2929

30+
import android.app.NotificationManager;
3031
import android.content.Context;
3132
import android.content.pm.ApplicationInfo;
3233
import android.content.pm.PackageManager;
3334
import android.database.Cursor;
3435
import android.database.sqlite.SQLiteDatabase;
36+
import android.os.Build;
3537
import android.os.Bundle;
38+
import android.service.notification.StatusBarNotification;
39+
import android.support.annotation.RequiresApi;
3640

3741
import com.onesignal.shortcutbadger.ShortcutBadger;
3842

3943
import com.onesignal.OneSignalDbContract.NotificationTable;
4044

41-
import static com.onesignal.NotificationRestorer.MAX_NUMBER_OF_NOTIFICATIONS_TO_RESTORE;
45+
import static com.onesignal.NotificationLimitManager.MAX_NUMBER_OF_NOTIFICATIONS_STR;
4246

4347
class BadgeCountUpdater {
4448

@@ -74,19 +78,43 @@ static void update(SQLiteDatabase readableDb, Context context) {
7478
if (!areBadgesEnabled(context))
7579
return;
7680

81+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
82+
updateStandard(context);
83+
else
84+
updateFallback(readableDb, context);
85+
}
86+
87+
@RequiresApi(api = Build.VERSION_CODES.M)
88+
private static void updateStandard(Context context) {
89+
NotificationManager notifManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
90+
StatusBarNotification[] activeNotifs = notifManager.getActiveNotifications();
91+
92+
int runningCount = 0;
93+
for (StatusBarNotification activeNotif : activeNotifs) {
94+
if (NotificationLimitManager.isGroupSummary(activeNotif))
95+
continue;
96+
runningCount++;
97+
}
98+
99+
updateCount(runningCount, context);
100+
}
101+
102+
private static void updateFallback(SQLiteDatabase readableDb, Context context) {
77103
Cursor cursor = readableDb.query(
78-
NotificationTable.TABLE_NAME,
79-
null,
80-
NotificationTable.recentUninteractedWithNotificationsWhere().toString(),
81-
null, // Where args
82-
null, // group by
83-
null, // filter by row groups
84-
null, // sort order, new to old
85-
MAX_NUMBER_OF_NOTIFICATIONS_TO_RESTORE
104+
NotificationTable.TABLE_NAME,
105+
null,
106+
NotificationTable.recentUninteractedWithNotificationsWhere().toString(),
107+
null, // Where args
108+
null, // group by
109+
null, // filter by row groups
110+
null, // sort order, new to old
111+
MAX_NUMBER_OF_NOTIFICATIONS_STR
86112
);
87113

88-
updateCount(cursor.getCount(), context);
114+
int notificationCount = cursor.getCount();
89115
cursor.close();
116+
117+
updateCount(notificationCount, context);
90118
}
91119

92120
static void updateCount(int count, Context context) {

OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,12 @@ private static void showNotification(NotificationGenerationJob notifJob) {
345345
// Keeps notification from playing sound + vibrating again
346346
if (notifJob.restoring)
347347
removeNotifyOptions(notifBuilder);
348-
348+
349+
int makeRoomFor = 1;
350+
if (group != null)
351+
makeRoomFor = 2;
352+
NotificationLimitManager.clearOldestOverLimit(currentContext, makeRoomFor);
353+
349354
Notification notification;
350355

351356
if (group != null) {
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.onesignal;
2+
3+
import android.app.Notification;
4+
import android.app.NotificationManager;
5+
import android.content.Context;
6+
import android.database.Cursor;
7+
import android.database.sqlite.SQLiteDatabase;
8+
import android.os.Build;
9+
import android.service.notification.StatusBarNotification;
10+
import android.support.annotation.RequiresApi;
11+
12+
import java.util.Map;
13+
import java.util.SortedMap;
14+
import java.util.TreeMap;
15+
16+
import com.onesignal.OneSignalDbContract.NotificationTable;
17+
18+
// Ensures old notifications are cleared up to a limit before displaying new ones
19+
class NotificationLimitManager {
20+
21+
// Android does not allow a package to have more than 49 total notifications being shown.
22+
// This limit prevents the following error;
23+
// E/NotificationService: Package has already posted 50 notifications.
24+
// Not showing more. package=####
25+
// Even though it says 50 in the error it is really a limit of 49.
26+
// See NotificationManagerService.java in the AOSP source
27+
//
28+
private static final int MAX_NUMBER_OF_NOTIFICATIONS_INT = 49;
29+
static final String MAX_NUMBER_OF_NOTIFICATIONS_STR = Integer.toString(MAX_NUMBER_OF_NOTIFICATIONS_INT);
30+
31+
private static int getMaxNumberOfNotificationsInt() {
32+
return MAX_NUMBER_OF_NOTIFICATIONS_INT;
33+
}
34+
35+
private static String getMaxNumberOfNotificationsString() {
36+
return MAX_NUMBER_OF_NOTIFICATIONS_STR;
37+
}
38+
39+
// Used to cancel the oldest notifications to make room for new notifications we are about to display
40+
// If we don't make this room users will NOT be alerted of new notifications for the app.
41+
static void clearOldestOverLimit(Context context, int notifsToMakeRoomFor) {
42+
System.out.println("getMaxNumberOfNotificationsInt(): " + getMaxNumberOfNotificationsInt());
43+
44+
try {
45+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
46+
clearOldestOverLimitStandard(context, notifsToMakeRoomFor);
47+
else
48+
clearOldestOverLimitFallback(context, notifsToMakeRoomFor);
49+
} catch(Throwable t) {
50+
// try-catch for Android 6.0.X bug work around, getActiveNotifications bug
51+
clearOldestOverLimitFallback(context, notifsToMakeRoomFor);
52+
}
53+
}
54+
55+
// Cancel the oldest notifications based on what the Android system reports is in the shade.
56+
// This could be any notification, not just a OneSignal notification
57+
@RequiresApi(api = Build.VERSION_CODES.M)
58+
static void clearOldestOverLimitStandard(Context context, int notifsToMakeRoomFor) throws Throwable {
59+
NotificationManager notifManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
60+
StatusBarNotification[] activeNotifs = notifManager.getActiveNotifications();
61+
62+
int notifsToClear = (activeNotifs.length - getMaxNumberOfNotificationsInt()) + notifsToMakeRoomFor;
63+
// We have enough room in the notification shade, no need to clear any notifications
64+
if (notifsToClear < 1)
65+
return;
66+
67+
// Create SortedMap so we can sort notifications based on display time
68+
SortedMap<Long, Integer> activeNotifIds = new TreeMap<>();
69+
for (StatusBarNotification activeNotif : activeNotifs) {
70+
if (isGroupSummary(activeNotif))
71+
continue;
72+
activeNotifIds.put(activeNotif.getNotification().when, activeNotif.getId());
73+
}
74+
75+
// Clear the oldest based on the count in notifsToClear
76+
for(Map.Entry<Long, Integer> mapData : activeNotifIds.entrySet()) {
77+
OneSignal.cancelNotification(mapData.getValue());
78+
if (--notifsToClear <= 0)
79+
break;
80+
}
81+
}
82+
83+
// This cancels any notifications based on the oldest in the local SQL database
84+
static void clearOldestOverLimitFallback(Context context, int notifsToMakeRoomFor) {
85+
OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(context);
86+
87+
Cursor cursor = null;
88+
try {
89+
SQLiteDatabase readableDb = dbHelper.getReadableDbWithRetries();
90+
cursor = readableDb.query(
91+
NotificationTable.TABLE_NAME,
92+
new String[] { NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID },
93+
NotificationTable.recentUninteractedWithNotificationsWhere().toString(),
94+
null,
95+
null,
96+
null,
97+
OneSignalDbContract.NotificationTable._ID, // sort order, old to new
98+
getMaxNumberOfNotificationsString() + notifsToMakeRoomFor // limit
99+
);
100+
101+
int notifsToClear = (cursor.getCount() - getMaxNumberOfNotificationsInt()) + notifsToMakeRoomFor;
102+
// We have enough room in the notification shade, no need to clear any notifications
103+
if (notifsToClear < 1)
104+
return;
105+
106+
while (cursor.moveToNext()) {
107+
int existingId = cursor.getInt(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID));
108+
OneSignal.cancelNotification(existingId);
109+
110+
if (--notifsToClear <= 0)
111+
break;
112+
}
113+
} catch (Throwable t) {
114+
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error clearing oldest notifications over limit! ", t);
115+
} finally {
116+
if (cursor != null && !cursor.isClosed())
117+
cursor.close();
118+
}
119+
}
120+
121+
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
122+
static boolean isGroupSummary(StatusBarNotification notif) {
123+
return (notif.getNotification().flags & Notification.FLAG_GROUP_SUMMARY) != 0;
124+
}
125+
}

OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationRestorer.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,6 @@ class NotificationRestorer {
8484
// E/NotificationService: Package enqueue rate is 10.56985. Shedding events. package=####
8585
private static final int DELAY_BETWEEN_NOTIFICATION_RESTORES_MS = 200;
8686

87-
// Android does not allow a package to have more than 49 total notifications being shown.
88-
// This limit prevents the following error;
89-
// E/NotificationService: Package has already posted 50 notifications.
90-
// Not showing more. package=####
91-
// Even though it says 50 in the error it is really a limit of 49.
92-
// See NotificationManagerService.java in the AOSP source
93-
static final String MAX_NUMBER_OF_NOTIFICATIONS_TO_RESTORE = "49";
94-
9587
// Notifications will never be force removed when the app's process is running,
9688
// so we only need to restore at most once per cold start of the app.
9789
public static boolean restored;
@@ -165,7 +157,7 @@ private static void queryAndRestoreNotificationsAndBadgeCount(
165157
null, // group by
166158
null, // filter by row groups
167159
NotificationTable._ID + " DESC", // sort order, new to old
168-
MAX_NUMBER_OF_NOTIFICATIONS_TO_RESTORE // limit
160+
NotificationLimitManager.MAX_NUMBER_OF_NOTIFICATIONS_STR // limit
169161
);
170162
showNotificationsFromCursor(context, cursor, DELAY_BETWEEN_NOTIFICATION_RESTORES_MS);
171163
BadgeCountUpdater.update(readableDb, context);
@@ -187,8 +179,6 @@ private static void skipVisibleNotifications(Context context, StringBuilder dbQu
187179
return;
188180

189181
NotificationManager notifManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
190-
if (notifManager == null)
191-
return;
192182

193183
try {
194184
StatusBarNotification[] activeNotifs = notifManager.getActiveNotifications();

OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2390,11 +2390,6 @@ public void complete(LocationGMS.LocationPoint point) {
23902390
* your app is restarted.
23912391
*/
23922392
public static void clearOneSignalNotifications() {
2393-
2394-
//if applicable, check if the user provided privacy consent
2395-
if (shouldLogUserPrivacyConsentErrorMessageForMethodName("clearOneSignalNotifications()"))
2396-
return;
2397-
23982393
Runnable runClearOneSignalNotifications = new Runnable() {
23992394
@Override
24002395
public void run() {
@@ -2477,11 +2472,6 @@ public void run() {
24772472
* @param id
24782473
*/
24792474
public static void cancelNotification(final int id) {
2480-
2481-
//if applicable, check if the user provided privacy consent
2482-
if (shouldLogUserPrivacyConsentErrorMessageForMethodName("cancelNotification()"))
2483-
return;
2484-
24852475
Runnable runCancelNotification = new Runnable() {
24862476
@Override
24872477
public void run() {
@@ -2516,6 +2506,9 @@ public void run() {
25162506
}
25172507
}
25182508
}
2509+
2510+
NotificationManager notificationManager = (NotificationManager)appContext.getSystemService(Context.NOTIFICATION_SERVICE);
2511+
notificationManager.cancel(id);
25192512
}
25202513
};
25212514

@@ -2529,9 +2522,6 @@ public void run() {
25292522
}
25302523

25312524
runCancelNotification.run();
2532-
2533-
NotificationManager notificationManager = (NotificationManager)appContext.getSystemService(Context.NOTIFICATION_SERVICE);
2534-
notificationManager.cancel(id);
25352525
}
25362526

25372527

OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,14 @@ public static void update(SQLiteDatabase readableDb, Context context) {
189189
com.onesignal.BadgeCountUpdater.update(readableDb, context);
190190
}
191191
}
192+
193+
static public class NotificationLimitManager extends com.onesignal.NotificationLimitManager {
194+
public static void clearOldestOverLimitFallback(Context context, int notifsToMakeRoomFor) {
195+
com.onesignal.NotificationLimitManager.clearOldestOverLimitFallback(context, notifsToMakeRoomFor);
196+
}
197+
198+
public static void clearOldestOverLimitStandard(Context context, int notifsToMakeRoomFor) throws Throwable {
199+
com.onesignal.NotificationLimitManager.clearOldestOverLimitStandard(context, notifsToMakeRoomFor);
200+
}
201+
}
192202
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.onesignal;
2+
3+
import org.robolectric.annotation.Implements;
4+
5+
@Implements(NotificationLimitManager.class)
6+
public class ShadowNotificationLimitManager {
7+
private static int MAX_NUMBER_OF_NOTIFICATIONS_INT = 2;
8+
9+
public static int getMaxNumberOfNotificationsInt() {
10+
return MAX_NUMBER_OF_NOTIFICATIONS_INT;
11+
}
12+
13+
public static String getMaxNumberOfNotificationsString() {
14+
return Integer.toString(MAX_NUMBER_OF_NOTIFICATIONS_INT);
15+
}
16+
}

OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2932,7 +2932,7 @@ public void shouldSetNotificationTypesToZeroWhenUnsubscribeWhenNotificationsAreD
29322932
public void shouldClearBadgesWhenPermissionIsDisabled() throws Exception {
29332933
OneSignalInit();
29342934
threadAndTaskWait();
2935-
ShadowBadgeCountUpdater.updateCount(1, blankActivity);
2935+
ShadowBadgeCountUpdater.lastCount = 1;
29362936

29372937
blankActivityController.pause();
29382938
threadAndTaskWait();

0 commit comments

Comments
 (0)