Skip to content

Commit 53dd9cc

Browse files
committed
Added notification scheduling service
1 parent d3026fc commit 53dd9cc

17 files changed

+771
-230
lines changed

android/app/src/main/AndroidManifest.xml

+4
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@
118118
android:name=".receivers.alarm.MidnightResetReceiver"
119119
android:enabled="true"
120120
android:exported="false" />
121+
<receiver
122+
android:name=".receivers.alarm.NotificationBatchReceiver"
123+
android:enabled="true"
124+
android:exported="false" />
121125
<!-- _________________________________________________________________________________ -->
122126
<!-- ________________________________ SERVICES _______________________________________ -->
123127
<!-- _________________________________________________________________________________ -->

android/app/src/main/java/com/mindful/android/helpers/AlarmTasksSchedulingHelper.java

+77-21
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
import static com.mindful.android.receivers.alarm.BedtimeRoutineReceiver.ACTION_ALERT_BEDTIME;
1616
import static com.mindful.android.receivers.alarm.BedtimeRoutineReceiver.ACTION_START_BEDTIME;
1717
import static com.mindful.android.receivers.alarm.BedtimeRoutineReceiver.ACTION_STOP_BEDTIME;
18+
import static com.mindful.android.receivers.alarm.NotificationBatchReceiver.ACTION_PUSH_BATCH;
19+
import static com.mindful.android.utils.AppConstants.ONE_DAY_IN_MS;
1820

1921
import android.app.AlarmManager;
2022
import android.app.PendingIntent;
2123
import android.content.Context;
2224
import android.content.Intent;
2325
import android.os.Build;
24-
import android.os.Handler;
2526
import android.util.Log;
2627

2728
import androidx.annotation.NonNull;
@@ -30,18 +31,23 @@
3031
import com.mindful.android.models.BedtimeSettings;
3132
import com.mindful.android.receivers.alarm.BedtimeRoutineReceiver;
3233
import com.mindful.android.receivers.alarm.MidnightResetReceiver;
34+
import com.mindful.android.receivers.alarm.NotificationBatchReceiver;
3335
import com.mindful.android.services.MindfulTrackerService;
3436
import com.mindful.android.utils.Utils;
3537

38+
import java.util.Arrays;
3639
import java.util.Calendar;
40+
import java.util.Collections;
3741
import java.util.Date;
42+
import java.util.HashSet;
43+
import java.util.List;
44+
import java.util.stream.Collectors;
3845

3946
/**
4047
* Helper class for scheduling alarm tasks related to bedtime routines and midnight resets.
4148
*/
4249
public class AlarmTasksSchedulingHelper {
4350
private static final String TAG = "Mindful.AlarmTasksSchedulingHelper";
44-
private static final long oneDayInMs = 24 * 60 * 60 * 1000;
4551

4652
/**
4753
* Schedules the midnight reset task if it is not already scheduled.
@@ -67,7 +73,7 @@ public static void scheduleMidnightResetTask(@NonNull Context context, boolean c
6773
cal.set(Calendar.SECOND, 3); // For safe side
6874
cal.add(Calendar.DATE, 1);
6975

70-
scheduleOrUpdateAlarmTask(context, MidnightResetReceiver.class, MidnightResetReceiver.ACTION_START_MIDNIGHT_RESET, cal.getTimeInMillis());
76+
scheduleOrUpdateExactAlarmTask(context, MidnightResetReceiver.class, MidnightResetReceiver.ACTION_START_MIDNIGHT_RESET, cal.getTimeInMillis());
7177
Log.d(TAG, "scheduleMidnightTask: Midnight reset task scheduled successfully for " + cal.getTime());
7278
}
7379

@@ -85,19 +91,19 @@ public static void scheduleBedtimeRoutineTasks(@NonNull Context context, @NonNul
8591

8692
// Bedtime is already ended then reschedule for the next day
8793
if (endTimeMs < nowInMs) {
88-
alertTimeMs += oneDayInMs;
89-
startTimeMs += oneDayInMs;
90-
endTimeMs += oneDayInMs;
94+
alertTimeMs += ONE_DAY_IN_MS;
95+
startTimeMs += ONE_DAY_IN_MS;
96+
endTimeMs += ONE_DAY_IN_MS;
9197
}
9298

9399
// If alert time is in future
94100
if (alertTimeMs > nowInMs) {
95-
scheduleOrUpdateAlarmTask(context, BedtimeRoutineReceiver.class, ACTION_ALERT_BEDTIME, alertTimeMs);
101+
scheduleOrUpdateExactAlarmTask(context, BedtimeRoutineReceiver.class, ACTION_ALERT_BEDTIME, alertTimeMs);
96102
}
97103

98104
// Bedtime start and stop tasks
99-
scheduleOrUpdateAlarmTask(context, BedtimeRoutineReceiver.class, ACTION_START_BEDTIME, startTimeMs);
100-
scheduleOrUpdateAlarmTask(context, BedtimeRoutineReceiver.class, ACTION_STOP_BEDTIME, endTimeMs);
105+
scheduleOrUpdateExactAlarmTask(context, BedtimeRoutineReceiver.class, ACTION_START_BEDTIME, startTimeMs);
106+
scheduleOrUpdateExactAlarmTask(context, BedtimeRoutineReceiver.class, ACTION_STOP_BEDTIME, endTimeMs);
101107
Log.d(TAG, "scheduleBedtimeStartTask: Bedtime routine tasks scheduled successfully for - "
102108
+ "\nalert: " + (alertTimeMs > nowInMs ? "" : "(skipping) ") + new Date(alertTimeMs)
103109
+ "\nstart: " + new Date(startTimeMs)
@@ -112,18 +118,8 @@ public static void scheduleBedtimeRoutineTasks(@NonNull Context context, @NonNul
112118
* @param context The application context.
113119
*/
114120
public static void cancelBedtimeRoutineTasks(@NonNull Context context) {
115-
Intent alertIntent = new Intent(context.getApplicationContext(), BedtimeRoutineReceiver.class).setAction(ACTION_ALERT_BEDTIME);
116-
Intent startIntent = new Intent(context.getApplicationContext(), BedtimeRoutineReceiver.class).setAction(ACTION_START_BEDTIME);
117-
Intent stopIntent = new Intent(context.getApplicationContext(), BedtimeRoutineReceiver.class).setAction(BedtimeRoutineReceiver.ACTION_STOP_BEDTIME);
118-
PendingIntent alertPendingIntent = PendingIntent.getBroadcast(context, 0, alertIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
119-
PendingIntent startPendingIntent = PendingIntent.getBroadcast(context, 0, startIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
120-
PendingIntent stopPendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
121-
122121
// Cancel the alarms
123-
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
124-
alarmManager.cancel(alertPendingIntent);
125-
alarmManager.cancel(startPendingIntent);
126-
alarmManager.cancel(stopPendingIntent);
122+
cancelExactAlarmTasks(context, BedtimeRoutineReceiver.class, Arrays.asList(ACTION_ALERT_BEDTIME, ACTION_START_BEDTIME, ACTION_STOP_BEDTIME));
127123

128124
// Let service know
129125
if (Utils.isServiceRunning(context, MindfulTrackerService.class.getName())) {
@@ -135,6 +131,47 @@ public static void cancelBedtimeRoutineTasks(@NonNull Context context) {
135131
Log.d(TAG, "cancelBedtimeRoutineTasks: Bedtime routine tasks cancelled successfully");
136132
}
137133

134+
/**
135+
* Schedules next future possible notification batch.
136+
*
137+
* @param context The application context.
138+
* @param scheduleTods The hashset of integers representing TODs in minutes.
139+
*/
140+
public static void scheduleNotificationBatchTask(@NonNull Context context, @NonNull HashSet<Integer> scheduleTods) {
141+
List<Integer> sortedTods = scheduleTods.stream().sorted().collect(Collectors.toList());
142+
if (sortedTods.isEmpty()) return;
143+
144+
long now = System.currentTimeMillis();
145+
Long nextAlarmTime = null;
146+
147+
// Find the first future TOD
148+
for (int tod : sortedTods) {
149+
long currentTime = Utils.todToTodayCal(tod).getTimeInMillis();
150+
if (currentTime > now) {
151+
nextAlarmTime = currentTime;
152+
break;
153+
}
154+
}
155+
156+
// If no future TOD, schedule for the first TOD of the next day
157+
if (nextAlarmTime == null) {
158+
nextAlarmTime = Utils.todToTodayCal(sortedTods.get(0)).getTimeInMillis() + ONE_DAY_IN_MS;
159+
}
160+
161+
scheduleOrUpdateExactAlarmTask(context, NotificationBatchReceiver.class, ACTION_PUSH_BATCH, nextAlarmTime);
162+
Log.d(TAG, "scheduleNotificationBatchTask: Notification batch task scheduled successfully for " + new Date(nextAlarmTime));
163+
}
164+
165+
/**
166+
* Cancels notification batch schedule task.
167+
*
168+
* @param context The application context.
169+
*/
170+
public static void cancelNotificationBatchTask(@NonNull Context context) {
171+
cancelExactAlarmTasks(context, NotificationBatchReceiver.NotificationBatchWorker.class, Collections.singletonList(ACTION_PUSH_BATCH));
172+
Log.d(TAG, "cancelNotificationBatchTask: Notification batch tasks cancelled successfully");
173+
}
174+
138175
/**
139176
* Schedules or updates an alarm task with the specified parameters.
140177
*
@@ -143,7 +180,7 @@ public static void cancelBedtimeRoutineTasks(@NonNull Context context) {
143180
* @param intentAction The action to be set on the intent.
144181
* @param epochTimeMs The time at which the alarm should go off, in milliseconds since epoch.
145182
*/
146-
private static void scheduleOrUpdateAlarmTask(@NonNull Context context, Class<?> receiverClass, String intentAction, long epochTimeMs) {
183+
private static void scheduleOrUpdateExactAlarmTask(@NonNull Context context, Class<?> receiverClass, String intentAction, long epochTimeMs) {
147184
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
148185
Intent intent = new Intent(context.getApplicationContext(), receiverClass).setAction(intentAction);
149186
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
@@ -156,4 +193,23 @@ private static void scheduleOrUpdateAlarmTask(@NonNull Context context, Class<?>
156193
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, epochTimeMs, pendingIntent);
157194
}
158195
}
196+
197+
/**
198+
* Cancels all the exact alarm task related to the service class and the list of actions.
199+
*
200+
* @param context The application context.
201+
* @param receiverClass The receiver class for the alarm.
202+
* @param intentActions The list of actions to be set on the intents.
203+
*/
204+
private static void cancelExactAlarmTasks(@NonNull Context context, Class<?> receiverClass, @NonNull List<String> intentActions) {
205+
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
206+
207+
for (String action : intentActions) {
208+
Intent intent = new Intent(context.getApplicationContext(), receiverClass).setAction(action);
209+
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
210+
alarmManager.cancel(pendingIntent);
211+
}
212+
}
213+
214+
159215
}

android/app/src/main/java/com/mindful/android/helpers/SharedPrefsHelper.java

+100-1
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121

2222
import com.mindful.android.models.AppRestrictions;
2323
import com.mindful.android.models.BedtimeSettings;
24+
import com.mindful.android.models.UpcomingNotification;
2425
import com.mindful.android.models.RestrictionGroup;
2526
import com.mindful.android.models.WellBeingSettings;
27+
import com.mindful.android.utils.AppConstants;
2628
import com.mindful.android.utils.JsonDeserializer;
2729
import com.mindful.android.utils.Utils;
2830

@@ -60,6 +62,12 @@ public class SharedPrefsHelper {
6062
private static final String CRASH_LOG_PREFS_BOX = "CrashLogPrefs";
6163
private static final String PREF_KEY_CRASH_LOGS = "crashLogs";
6264

65+
private static SharedPreferences mNotificationBatchPrefs;
66+
private static final String NOTIFICATION_BATCH_PREFS_BOX = "NotificationBatchPrefs";
67+
private static final String PREF_KEY_UPCOMING_NOTIFICATIONS = "upcomingNotifications";
68+
private static final String PREF_KEY_BATCH_SCHEDULES = "batchSchedules";
69+
private static final String PREF_KEY_BATCHED_APPS = "batchedApps";
70+
6371
private static void checkAndInitializeUniquePrefs(@NonNull Context context) {
6472
if (mUniquePrefs != null) return;
6573
mUniquePrefs = context.getApplicationContext().getSharedPreferences(UNIQUE_PREFS_BOX, Context.MODE_PRIVATE);
@@ -80,6 +88,11 @@ private static void checkAndInitializeCrashLogPrefs(@NonNull Context context) {
8088
mCrashLogPrefs = context.getApplicationContext().getSharedPreferences(CRASH_LOG_PREFS_BOX, Context.MODE_PRIVATE);
8189
}
8290

91+
private static void checkAndInitializeNotificationBatchPrefs(@NonNull Context context) {
92+
if (mNotificationBatchPrefs != null) return;
93+
mNotificationBatchPrefs = context.getApplicationContext().getSharedPreferences(NOTIFICATION_BATCH_PREFS_BOX, Context.MODE_PRIVATE);
94+
}
95+
8396

8497
/**
8598
* Checks and Migrates shared prefs data from old one file to multiple new files.
@@ -300,7 +313,7 @@ public static void insertCrashLogToPrefs(@NonNull Context context, @NonNull Thro
300313

301314

302315
/**
303-
* Retrieves the crash logs from SharedPreferences as a JSON string.
316+
* Retrieves the crash logs from SharedPreferences as a JSON Array string.
304317
*
305318
* @param context The application context used to access SharedPreferences.
306319
* @return A JSON string representing the stored crash logs array.
@@ -320,4 +333,90 @@ public static void clearCrashLogs(@NonNull Context context) {
320333
checkAndInitializeCrashLogPrefs(context);
321334
mCrashLogPrefs.edit().putString(PREF_KEY_CRASH_LOGS, "[]").apply();
322335
}
336+
337+
338+
/**
339+
* Fetches the hashset of integers representing notification schedules if jsonNotificationSchedules is null else store it's json.
340+
*
341+
* @param context The application context.
342+
* @param jsonNotificationSchedules The JSON string of hashset of integers representing notification schedules.
343+
*/
344+
@NonNull
345+
public static HashSet<Integer> getSetNotificationBatchSchedules(@NonNull Context context, @Nullable String jsonNotificationSchedules) {
346+
checkAndInitializeNotificationBatchPrefs(context);
347+
if (jsonNotificationSchedules == null) {
348+
return JsonDeserializer.jsonStrToIntegerHashSet(mNotificationBatchPrefs.getString(PREF_KEY_BATCH_SCHEDULES, ""));
349+
} else {
350+
mNotificationBatchPrefs.edit().putString(PREF_KEY_BATCH_SCHEDULES, jsonNotificationSchedules).apply();
351+
return JsonDeserializer.jsonStrToIntegerHashSet(jsonNotificationSchedules);
352+
}
353+
}
354+
355+
/**
356+
* Creates and Inserts a new notification into SharedPreferences based on the passed object.
357+
*
358+
* @param context The application context used to retrieve app version and store the log.
359+
* @param notification The notification which will be inserted as map.
360+
*/
361+
public static void insertNotificationToPrefs(@NonNull Context context, @NonNull UpcomingNotification notification) {
362+
checkAndInitializeNotificationBatchPrefs(context);
363+
364+
// Create new notification object
365+
JSONObject currentNotification = new JSONObject(notification.toMap());
366+
367+
// Get existing notifications
368+
String notificationsJson = mNotificationBatchPrefs.getString(PREF_KEY_UPCOMING_NOTIFICATIONS, "[]");
369+
JSONArray notificationsArray = null;
370+
try {
371+
notificationsArray = new JSONArray(notificationsJson);
372+
373+
// Remove notifications older than 24 hours
374+
long last24Time = System.currentTimeMillis() - AppConstants.ONE_DAY_IN_MS;
375+
376+
for (int i = 0; i < notificationsArray.length(); i++) {
377+
long timeStamp = notificationsArray.getJSONObject(i).getLong("timeStamp");
378+
if (timeStamp < last24Time) {
379+
notificationsArray.remove(i);
380+
}
381+
}
382+
383+
} catch (Exception e1) {
384+
notificationsArray = new JSONArray();
385+
}
386+
387+
// Insert current notification and update prefs
388+
notificationsArray.put(currentNotification);
389+
mNotificationBatchPrefs.edit().putString(PREF_KEY_UPCOMING_NOTIFICATIONS, notificationsArray.toString()).apply();
390+
}
391+
392+
393+
/**
394+
* Retrieves the list of notification from SharedPreferences as a JSON Array string.
395+
*
396+
* @param context The application context used to access SharedPreferences.
397+
* @return A JSON string representing the stored notifications array.
398+
*/
399+
@NonNull
400+
public static String getUpComingNotificationsArrayString(@NonNull Context context) {
401+
checkAndInitializeNotificationBatchPrefs(context);
402+
return mNotificationBatchPrefs.getString(PREF_KEY_UPCOMING_NOTIFICATIONS, "[]");
403+
}
404+
405+
/**
406+
* Fetches the hashset of notification batched apps if jsonBatchedApps is null else store it's json.
407+
*
408+
* @param context The application context.
409+
* @param jsonBatchedApps The JSON string of notification batched apps.
410+
*/
411+
@NonNull
412+
public static HashSet<String> getSetNotificationBatchedApps(@NonNull Context context, @Nullable String jsonBatchedApps) {
413+
checkAndInitializeNotificationBatchPrefs(context);
414+
if (jsonBatchedApps == null) {
415+
return JsonDeserializer.jsonStrToStringHashSet(mNotificationBatchPrefs.getString(PREF_KEY_BATCHED_APPS, ""));
416+
} else {
417+
mNotificationBatchPrefs.edit().putString(PREF_KEY_BATCHED_APPS, jsonBatchedApps).apply();
418+
return JsonDeserializer.jsonStrToStringHashSet(jsonBatchedApps);
419+
}
420+
}
421+
323422
}

android/app/src/main/java/com/mindful/android/models/PendingNotification.java

-25
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.mindful.android.models;
2+
3+
4+
import android.app.Notification;
5+
import android.os.Bundle;
6+
import android.service.notification.StatusBarNotification;
7+
8+
import androidx.annotation.NonNull;
9+
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
/**
14+
* Represents an App Notification with its metadata.
15+
*/
16+
public class UpcomingNotification {
17+
public final String packageName;
18+
public final String titleText;
19+
public final String contentText;
20+
public final long timeStamp;
21+
22+
// Constructor that initializes the object using StatusBarNotification
23+
public UpcomingNotification(@NonNull StatusBarNotification sbn) {
24+
Bundle extras = sbn.getNotification().extras;
25+
this.packageName = sbn.getPackageName();
26+
this.titleText = extras.getString(Notification.EXTRA_TITLE, "Null").trim();
27+
this.contentText = extras.getString(Notification.EXTRA_TEXT, "Null").trim();
28+
this.timeStamp = sbn.getPostTime();
29+
}
30+
31+
/**
32+
* Converts the UpcomingNotification object to a map of string keys to object values.
33+
* The map can be used for serialization or other purposes.
34+
*
35+
* @return A map representing the UpcomingNotification object.
36+
*/
37+
public Map<String, Object> toMap() {
38+
Map<String, Object> notificationMap = new HashMap<>();
39+
notificationMap.put("packageName", packageName);
40+
notificationMap.put("titleText", titleText);
41+
notificationMap.put("contentText", contentText);
42+
notificationMap.put("timeStamp", timeStamp);
43+
return notificationMap;
44+
}
45+
}

0 commit comments

Comments
 (0)