Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ public class AppNotificationManager {
private static final String CHANNEL_NAME = "CHT Android Notifications";
public static final int REQUEST_NOTIFICATION_PERMISSION = 1001;
public static final String TASK_NOTIFICATIONS_KEY = "task_notifications";
public static final String TASK_NOTIFICATION_SETTINGS_KEY = "task_notification_settings";
public static final String TASK_NOTIFICATION_DAY_KEY = "cht_task_notification_day";
public static final String LATEST_NOTIFICATION_TIMESTAMP_KEY = "cht_task_notification_timestamp";
public static final String MAX_NOTIFICATIONS_TO_SHOW_KEY = "cht_max_task_notifications";

private final Context context;
private final NotificationManager manager;
Expand Down Expand Up @@ -114,7 +114,7 @@ public void showNotificationsFromJsArray(String jsArrayString) throws JSONExcept
private void showMultipleTaskNotifications(JSONArray dataArray) throws JSONException {
Intent intent = new Intent(context, EmbeddedBrowserActivity.class);
intent.setData(Uri.parse(TextUtils.concat(appUrl, "/#/tasks").toString()));
long maxNotifications = appDataStore.getLongBlocking(MAX_NOTIFICATIONS_TO_SHOW_KEY, 8L);
long maxNotifications = getSettings(appDataStore).getLong("maxNotifications");
long latestStoredTimestamp = getLatestStoredTimestamp(getStartOfDay());
int counter = 0;
long latestReadyAt = 0;
Expand Down Expand Up @@ -148,9 +148,15 @@ public void saveLatestNotificationTimestamp(long value) {
appDataStore.saveLong(LATEST_NOTIFICATION_TIMESTAMP_KEY, value);
}

public static JSONObject getSettings(AppDataStore dataStore){
String settings = dataStore.getStringBlocking(TASK_NOTIFICATION_SETTINGS_KEY, "{}");
return Utils.parseJSONObject(settings);
}

public long getStartOfDay() {
return LocalDate.now()
.atStartOfDay(ZoneId.systemDefault())
ZoneId zone = ZoneId.systemDefault();
return LocalDate.now(zone)
.atStartOfDay(zone)
.toInstant().toEpochMilli();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,18 @@ public void mrdt_verify() {
}
}

//CHT-Core v5.1 and v5.2 use this
@JavascriptInterface
public void updateTaskNotificationStore(String notifications, long maxNotifications) {
String settings = String.format("{maxNotifications: %s}", maxNotifications);
updateTaskNotificationStoreWithSettings(notifications, settings);
}

@JavascriptInterface
public void updateTaskNotificationStoreWithSettings(
String notifications, String settings) {
AppDataStore appDataStore = AppDataStore.getInstance(parent.getApplicationContext());
appDataStore.saveLong(AppNotificationManager.MAX_NOTIFICATIONS_TO_SHOW_KEY, maxNotifications);
appDataStore.saveString(AppNotificationManager.TASK_NOTIFICATIONS_KEY, notifications);
appDataStore.saveTaskNotificationSettingsBlocking(settings, notifications);
}

@android.webkit.JavascriptInterface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
import androidx.work.WorkerParameters;

import org.json.JSONException;
import org.json.JSONObject;
import org.medicmobile.webapp.mobile.util.AppDataStore;

import java.time.LocalTime;
import java.time.ZoneId;

public class NotificationWorker extends Worker {
public static final String NOTIFICATION_WORK_REQUEST_TAG = "cht_notification_tag";
public static final String NOTIFICATION_WORK_NAME = "appNotifications";
Expand All @@ -27,13 +31,29 @@ public Result doWork() {
AppDataStore appDataStore = AppDataStore.getInstance(context);
AppNotificationManager appNotificationManager = new AppNotificationManager(context);
try {
String result = appDataStore
.getStringBlocking(AppNotificationManager.TASK_NOTIFICATIONS_KEY, "[]");
appNotificationManager.showNotificationsFromJsArray(result);
JSONObject notificationWindowSettings = AppNotificationManager.getSettings(appDataStore);
if (isNotificationWindow(notificationWindowSettings)) {
String notifications = appDataStore
.getStringBlocking(AppNotificationManager.TASK_NOTIFICATIONS_KEY, "[]");
appNotificationManager.showNotificationsFromJsArray(notifications);
}
return Result.success();
} catch (JSONException e) {
log(e, "error showing notifications");
return Result.failure();
}
}

boolean isNotificationWindow(JSONObject settings) throws JSONException {
if (!settings.has("start") || !settings.has("end")) {
return true;
}
LocalTime start = Utils.formatTime(settings.getString("start"));
LocalTime end = Utils.formatTime(settings.getString("end"));
if (start == null || end == null) {
return false;
}
LocalTime now = LocalTime.now(ZoneId.systemDefault());
return now.isAfter(start) && now.isBefore(end);
}
}
39 changes: 32 additions & 7 deletions src/main/java/org/medicmobile/webapp/mobile/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
import org.json.JSONObject;

import java.io.File;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Optional;

final class Utils {
private Utils() {}
private Utils() {
}

/**
* @see #isValidNavigationUrl(String, String)
Expand Down Expand Up @@ -57,17 +60,17 @@ static boolean isUrlRelated(String appUrl, String uriToTest) {
*/
static boolean isValidNavigationUrl(String appUrl, String navUrl) {
boolean isValid = isUrlRelated(appUrl, navUrl);
if (isValid && !navUrl.matches(".*/(login|_rewrite).*")) {
if (isValid && !navUrl.matches(".*/(login|_rewrite).*")) { // NOSONAR
return true;
}
return false;
}

static JSONObject json(Object... keyVals) throws JSONException {
if(DEBUG && keyVals.length % 2 != 0) throw new AssertionError();
if (DEBUG && keyVals.length % 2 != 0) throw new AssertionError();
JSONObject o = new JSONObject();
for(int i=keyVals.length-1; i>0; i-=2) {
o.put(keyVals[i-1].toString(), keyVals[i]);
for (int i = keyVals.length - 1; i > 0; i -= 2) {
o.put(keyVals[i - 1].toString(), keyVals[i]);
}
return o;
}
Expand All @@ -77,7 +80,7 @@ static boolean intentHandlerAvailableFor(Context ctx, Intent intent) {
}

static void startAppActivityChain(Activity a) {
if(SettingsStore.in(a).hasWebappSettings()) {
if (SettingsStore.in(a).hasWebappSettings()) {
a.startActivity(new Intent(a, EmbeddedBrowserActivity.class));
} else {
a.startActivity(new Intent(a, SettingsDialogActivity.class));
Expand All @@ -86,7 +89,7 @@ static void startAppActivityChain(Activity a) {
}

static String createUseragentFrom(String current) {
if(current.contains(APPLICATION_ID)) return current;
if (current.contains(APPLICATION_ID)) return current;

return String.format("%s %s/%s",
current, APPLICATION_ID, VERSION_NAME);
Expand All @@ -101,6 +104,7 @@ static void restartApp(Context context) {

/**
* The file path can be a regular file or a content ("content://" scheme)
*
* @param path {String} File path
* @return {Uri}
*/
Expand Down Expand Up @@ -128,6 +132,7 @@ static boolean isDebug() {

/**
* parses js string array to JSONArray
*
* @param stringArray {String} array in string format
* @return {JSONArray}
*/
Expand All @@ -140,6 +145,26 @@ static JSONArray parseJSArrayData(String stringArray) {
}
}

static JSONObject parseJSONObject(String inputObject) {
try {
return new JSONObject(inputObject);
} catch (JSONException e) {
log(e, "error parsing object");
return new JSONObject();
}
}

//formats string time to HH:mm
static LocalTime formatTime(String timeString) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
try {
return LocalTime.parse(timeString, formatter);
} catch (Exception e) {
log(e, "Error parsing window time");
return null;
}
}

static boolean checkIfDomainsAreVerified(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return true;
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/org/medicmobile/webapp/mobile/util/AppDataStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
import androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder;
import androidx.datastore.rxjava3.RxDataStore;

import org.medicmobile.webapp.mobile.AppNotificationManager;

import java.util.concurrent.TimeUnit;

import io.reactivex.rxjava3.core.Single;

@OptIn(markerClass = kotlinx.coroutines.ExperimentalCoroutinesApi.class)
public class AppDataStore {
private static final String DATASTORE_NAME = "cht_datastore";
private static final long WRITE_TIMEOUT_SECONDS = 5;
private static AppDataStore instance;
private final RxDataStore<Preferences> dataStore;

Expand Down Expand Up @@ -60,6 +65,11 @@ public void saveLongBlocking(String key, Long value) {
Preferences ignored = updateResult.blockingGet(); // NOSONAR
}

public void saveStringBlocking(String key, String value) {
Single<Preferences> updateResult = save(PreferencesKeys.stringKey(key), value);
Preferences ignored = updateResult.blockingGet(); // NOSONAR
}

private <T> T getBlocking(Key<T> key, @Nullable T defaultValue) {
return dataStore
.data()
Expand All @@ -77,4 +87,25 @@ public String getStringBlocking(String key, @Nullable String defaultValue) {
public long getLongBlocking(String key, @Nullable Long defaultValue) {
return getBlocking(PreferencesKeys.longKey(key), defaultValue);
}

/**
* Persists the task-notifications, settings and max-count in a single atomic transaction.
*/
public void saveTaskNotificationSettingsBlocking(String settings, String notifications) {
try {
Preferences ignored = dataStore // NOSONAR
.updateDataAsync(preferences -> {
MutablePreferences mutablePreferences = preferences.toMutablePreferences();
mutablePreferences.set(PreferencesKeys
.stringKey(AppNotificationManager.TASK_NOTIFICATION_SETTINGS_KEY), settings);
mutablePreferences.set(PreferencesKeys
.stringKey(AppNotificationManager.TASK_NOTIFICATIONS_KEY), notifications);
return Single.just(mutablePreferences);
})
.timeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.blockingGet();
} catch (Exception e) {
log(e, "AppDataStore :: saving task notification settings failed/timed out");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public void setup() {
appNotificationManager = spy(new AppNotificationManager(context));
startOfDay = appNotificationManager.getStartOfDay();
appDataStore = AppDataStore.getInstance(context);
appDataStore.saveStringBlocking(AppNotificationManager.TASK_NOTIFICATION_SETTINGS_KEY, "{\"maxNotifications\": 8}");
useBlockingDataStoreGet();
}

Expand All @@ -46,7 +47,7 @@ public void resetDataStore() {
appDataStore.saveString(AppNotificationManager.TASK_NOTIFICATIONS_KEY, "[]");
appDataStore.saveLong(AppNotificationManager.TASK_NOTIFICATION_DAY_KEY, 0L);
appDataStore.saveLong(AppNotificationManager.LATEST_NOTIFICATION_TIMESTAMP_KEY, 0L);
appDataStore.saveLong(AppNotificationManager.MAX_NOTIFICATIONS_TO_SHOW_KEY, 8L);
appDataStore.saveString(AppNotificationManager.TASK_NOTIFICATION_SETTINGS_KEY, "{\"maxNotifications\": 8}");
}

@Test
Expand All @@ -65,7 +66,7 @@ public void showsAndDismissesAllNotifications() throws JSONException {
@Test
public void showsOnlyMaxAllowedNotifications() throws JSONException {
String jsData = "[" + getJSTaskNotificationString(startOfDay, startOfDay, startOfDay) + "]";
appDataStore.saveLongBlocking(AppNotificationManager.MAX_NOTIFICATIONS_TO_SHOW_KEY, 1L);
appDataStore.saveStringBlocking(AppNotificationManager.TASK_NOTIFICATION_SETTINGS_KEY, "{\"maxNotifications\": 1}");
appNotificationManager.showNotificationsFromJsArray(jsData);
assertEquals(1, shadowNotificationManager.getAllNotifications().size());
}
Expand All @@ -87,7 +88,7 @@ public void respectsNotificationsOrderAndStoreLatestReadyAtTimestamp() throws JS
}
]
""".formatted(jsData, latestReadyAtTimestamp, startOfDay, startOfDay);
appDataStore.saveLongBlocking(AppNotificationManager.MAX_NOTIFICATIONS_TO_SHOW_KEY, 1L);
appDataStore.saveStringBlocking(AppNotificationManager.TASK_NOTIFICATION_SETTINGS_KEY, "{\"maxNotifications\": 1}");
appNotificationManager.showNotificationsFromJsArray(newNotificationData);
assertEquals(1, shadowNotificationManager.getAllNotifications().size());
assertEquals(latestReadyAtTimestamp, appDataStore.getLongBlocking(AppNotificationManager.LATEST_NOTIFICATION_TIMESTAMP_KEY, 0L));
Expand Down
Loading
Loading