From de39292bcb4f5862f95b3dd84bfa433b1ae17495 Mon Sep 17 00:00:00 2001 From: Bence Szasz Date: Fri, 6 Oct 2023 12:25:23 +0200 Subject: [PATCH] reCAPTHCA fine-tuning --- .../CareLinkFollowDownloader.java | 97 +++++++++---- .../carelinkfollow/CareLinkFollowService.java | 135 ++++++++++++------ .../auth/CareLinkAuthenticator.java | 5 +- .../auth/CareLinkCredentialStore.java | 7 + .../carelinkfollow/client/CareLinkClient.java | 1 - .../cgm/carelinkfollow/utils/Logger.java | 12 ++ .../dexdrip/utils/Preferences.java | 4 +- wear/build.gradle | 2 +- 8 files changed, 190 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/utils/Logger.java diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/CareLinkFollowDownloader.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/CareLinkFollowDownloader.java index 06db587..272a255 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/CareLinkFollowDownloader.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/CareLinkFollowDownloader.java @@ -10,8 +10,8 @@ import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkCredentialStore; import com.eveningoutpost.dexdrip.cgm.carelinkfollow.client.*; import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.RecentData; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.utils.Logger; -import static com.eveningoutpost.dexdrip.Models.JoH.dateText; import static com.eveningoutpost.dexdrip.Models.JoH.emptyString; public class CareLinkFollowDownloader { @@ -61,6 +61,7 @@ public boolean doEverything() { if (D) UserError.Log.e(TAG, "doEverything called"); //if (loginDataLooksOkay) { if (CareLinkCredentialStore.getInstance().getAuthStatus() == CareLinkCredentialStore.AUTHENTICATED) { + //Refresh token if expiration if (CareLinkCredentialStore.getInstance().getExpiresIn() < 6 * 60_000) { UserError.Log.e(TAG, "Token is about to expire, trying to renew it."); try { @@ -119,6 +120,68 @@ public boolean doEverything() { } + public void doEverything(boolean refreshToken, boolean downloadData) { + Logger.Log(TAG, "doEverything"); + if (refreshToken) + this.refreshToken(); + if (downloadData) + this.downloadData(); + } + + private void downloadData() { + Logger.Log(TAG, "downloadData"); + msg("Start download"); + if (checkCredentials()) { + try { + if (getCareLinkClient() != null) { + extendWakeLock(30_000); + backgroundProcessConnectData(); + } else { + UserError.Log.d(TAG, "Cannot get data as CareLinkClient is null"); + msg("Download data failed!"); + } + } catch (Exception e) { + UserError.Log.e(TAG, "Got exception in getData() " + e); + releaseWakeLock(); + msg("Download data failed!"); + } + } + } + + private void refreshToken() { + Logger.Log(TAG, "refreshToken"); + msg("Start refreshing token"); + if (checkCredentials()) { + try { + if (new CareLinkAuthenticator(CareLinkCredentialStore.getInstance().getCredential().country, CareLinkCredentialStore.getInstance()).refreshToken()) { + UserError.Log.e(TAG, "Login token renewed!"); + msg(null); + } else { + UserError.Log.e(TAG, "Error renewing login token!"); + msg("Login refresh failed! Will try again!"); + } + } catch (Exception e) { + UserError.Log.e(TAG, "Error renewing login token: " + e.getMessage()); + msg("Login refresh failed! Will try again!"); + } + } + } + + private boolean checkCredentials() { + // Not authenticated + if (CareLinkCredentialStore.getInstance().getAuthStatus() != CareLinkCredentialStore.AUTHENTICATED) { + msg("Not logged in! Please log in!"); + return false; + // Token expired + } else if (CareLinkCredentialStore.getInstance().getExpiresIn() <= 0) { + msg("Login refresh expired! Please log in!"); + return false; + // Credentials are all ok! + } else { + return true; + } + } + private void msg(final String msg) { status = msg != null ? JoH.hourMinuteString() + ": " + msg : null; if (msg != null) UserError.Log.d(TAG, "Setting message: " + status); @@ -136,17 +199,13 @@ private void backgroundProcessConnectData() { // don't call this directly unless you are also handling the wakelock release private void processCareLinkData() { + Logger.Log(TAG, "processCareLinkData"); RecentData recentData = null; CareLinkClient carelinkClient = null; //Get client carelinkClient = getCareLinkClient(); - //Get ConnectData from CareLink client if (carelinkClient != null) { - - //Try twice in case of 401 error - for (int i = 0; i < 2; i++) { - //Get data try { if (JoH.emptyString(this.carelinkPatient)) @@ -157,16 +216,13 @@ private void processCareLinkData() { } catch (Exception e) { UserError.Log.e(TAG, "Exception in CareLink data download: " + e); } - //Process data if (recentData != null) { UserError.Log.d(TAG, "Success get data!"); //Process data try { - if (D) UserError.Log.d(TAG, "Start process data"); //Process CareLink data (conversion and update xDrip data) CareLinkDataProcessor.processRecentData(recentData, true); - if (D) UserError.Log.d(TAG, "ProcessData finished!"); //Update Service status CareLinkFollowService.updateBgReceiveDelay(); msg(null); @@ -174,34 +230,19 @@ private void processCareLinkData() { UserError.Log.e(TAG, "Exception in data processing: " + e); msg("Data processing error!"); } - //Data receive error + //Data receive error } else { - //first 401 error => TRY AGAIN, only debug log - if (carelinkClient.getLastResponseCode() == 401 && i == 0) { - UserError.Log.d(TAG, "Try get data again due to 401 response code." + getCareLinkClient().getLastErrorMessage()); - //second 401 error => unauthorized error - } else if (carelinkClient.getLastResponseCode() == 401) { + if (carelinkClient.getLastResponseCode() == 401) { UserError.Log.e(TAG, "CareLink login error! Response code: " + carelinkClient.getLastResponseCode()); msg("Login error!"); //login error - } else if (!getCareLinkClient().getLastLoginSuccess()) { - UserError.Log.e(TAG, "CareLink login error! Response code: " + carelinkClient.getLastResponseCode()); - UserError.Log.e(TAG, "Error message: " + getCareLinkClient().getLastErrorMessage()); - msg("Login error!"); - //other error in download } else { UserError.Log.e(TAG, "CareLink download error! Response code: " + carelinkClient.getLastResponseCode()); UserError.Log.e(TAG, "Error message: " + getCareLinkClient().getLastErrorMessage()); - msg("Data request error!"); + msg("Download data failed!"); } } - //Next try only for 401 error and first attempt - if (!(carelinkClient.getLastResponseCode() == 401 && i == 0)) - break; - - } - } } @@ -215,7 +256,7 @@ private CareLinkClient getCareLinkClient() { carelinkClient = new CareLinkClient(CareLinkCredentialStore.getInstance()); //carelinkClient = new CareLinkClient(carelinkUsername, carelinkPassword, carelinkCountry); } catch (Exception e) { - UserError.Log.e(TAG, "Error creating CareLinkClient"); + UserError.Log.e(TAG, "Error creating CareLinkClient: " + e.getMessage()); } } return carelinkClient; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/CareLinkFollowService.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/CareLinkFollowService.java index c5d8f84..e9f33c1 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/CareLinkFollowService.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/CareLinkFollowService.java @@ -14,6 +14,7 @@ import com.eveningoutpost.dexdrip.UtilityModels.Pref; import com.eveningoutpost.dexdrip.UtilityModels.StatusItem; import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkCredentialStore; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.utils.Logger; import com.eveningoutpost.dexdrip.utils.DexCollectionType; import com.eveningoutpost.dexdrip.utils.framework.BuggySamsung; import com.eveningoutpost.dexdrip.utils.framework.ForegroundService; @@ -27,12 +28,15 @@ import static com.eveningoutpost.dexdrip.Models.JoH.msSince; import static com.eveningoutpost.dexdrip.UtilityModels.BgGraphBuilder.DEXCOM_PERIOD; +import org.apache.commons.math3.complex.Quaternion; + public class CareLinkFollowService extends ForegroundService { private static final String TAG = "CareLinkFollow"; private static final long SAMPLE_PERIOD = DEXCOM_PERIOD; - - private static final boolean DEBUG_TIMING = false; + private static final int RATE_LIMIT_SECONDS = 20; + private static final String RATE_LIMIT_NAME = "last-carelink-follow-poll"; + private static final int RATE_LIMIT_SAFETY = 10; protected static volatile String lastState = ""; @@ -47,12 +51,15 @@ public class CareLinkFollowService extends ForegroundService { private static CareLinkFollowDownloader downloader; private static volatile int gracePeriod = 0; private static volatile int pollInterval = 0; + private static volatile int renewBefore = 0; + private static volatile int renewInterval = 0; private static final long WAKE_UP_GRACE_SECOND = 60; @Override public void onCreate() { + Logger.Log(TAG, "onCreate"); super.onCreate(); resetInstance(); // manage static reference life cycle } @@ -62,6 +69,7 @@ public void onCreate() { * Update observedDelay if new bg reading is available */ static void updateBgReceiveDelay() { + Logger.Log(TAG, "updateBgReceiveDelay"); lastBg = BgReading.lastNoSenssor(); if (lastBg != null && lastBgTime != lastBg.timestamp) { bgReceiveDelay = JoH.msSince(lastBg.timestamp); @@ -70,6 +78,7 @@ static void updateBgReceiveDelay() { } public synchronized static void resetInstanceAndInvalidateSession() { + Logger.Log(TAG, "resetInstanceAndInvalidateSession"); try { if (downloader != null) { downloader.invalidateSession(); @@ -84,6 +93,8 @@ public static void resetInstance() { downloader = null; gracePeriod = 0; pollInterval = 0; + renewBefore = 0; + renewInterval = 0; } private static boolean shouldServiceRun() { @@ -95,58 +106,102 @@ private static long getGraceMillis() { return Constants.SECOND_IN_MS * gracePeriod; } + private static long getRenewBeforeMillis() { + return Constants.MINUTE_IN_MS * renewBefore; + } + + private static long getRenewIntervalMillis() { + return Constants.MINUTE_IN_MS * renewInterval; + } + private static long getIntervalMillis() { - //return Constants.SECOND_IN_MS * WAKE_UP_GRACE_SECOND; - if (pollInterval == 0) { - if (DEBUG_TIMING) UserError.Log.d(TAG, "POLL INTERVAL IS 0 !!!"); - return SAMPLE_PERIOD; - } else - return Constants.MINUTE_IN_MS * pollInterval; + return Constants.MINUTE_IN_MS * pollInterval; + } + + private static CareLinkFollowDownloader getDownloader(){ + if (downloader == null) { + downloader = new CareLinkFollowDownloader( + Pref.getString("clfollow_user", ""), + Pref.getString("clfollow_pass", ""), + Pref.getString("clfollow_country", "").toLowerCase(), + Pref.getString("clfollow_patient", "") + ); + } + return downloader; } static void scheduleWakeUp() { + + Logger.Log(TAG, "scheduleWakeUp"); + String scheduleReason; + long next; + final BgReading lastBg = BgReading.lastNoSenssor(); final long last = lastBg != null ? lastBg.timestamp : 0; - final long next = anticipateNextWakeUp(JoH.tsl(), last, SAMPLE_PERIOD, getGraceMillis(), getIntervalMillis()); - wakeup_time = next; - UserError.Log.d(TAG, "Anticipate next: " + JoH.dateTimeText(next) + " last BG timestamp: " + JoH.dateTimeText(last)); + final long nextTokenRefresh = anticipateNextTokenRefresh(JoH.tsl(), CareLinkCredentialStore.getInstance().getExpiresOn(), getRenewBeforeMillis(), getRenewIntervalMillis()); + final long nextDataPoll = anticipateNextDataPoll(JoH.tsl(), last, SAMPLE_PERIOD, getGraceMillis(), getIntervalMillis()); + + // Token needs to refreshed sooner + if(nextTokenRefresh <= nextDataPoll){ + next = nextTokenRefresh; + scheduleReason = " as login expires: "; + // Data is required sooner + } else { + next = nextDataPoll; + scheduleReason = " as last BG timestamp: "; + } + + if(JoH.msTill(next) < (RATE_LIMIT_SECONDS * Constants.SECOND_IN_MS)) + next = JoH.tsl() + (RATE_LIMIT_SECONDS * Constants.SECOND_IN_MS); + + UserError.Log.d(TAG, "Anticipate next: " + JoH.dateTimeText(next) + scheduleReason + JoH.dateTimeText(last)); + wakeup_time = next; JoH.wakeUpIntent(xdrip.getAppContext(), JoH.msTill(next), WakeLockTrampoline.getPendingIntent(CareLinkFollowService.class, Constants.CARELINKFOLLOW_SERVICE_FAILOVER_ID)); } - public static long anticipateNextWakeUp(long now, final long last, final long period, final long grace, final long interval) { + long nextDataPoll = anticipateNextDataPoll(now, last, period, grace, interval); + + return nextDataPoll; + + } + + private static long anticipateNextTokenRefresh(long now, final long expiry, final long before, final long interval){ + long next; - long expectedLast; - if (DEBUG_TIMING) UserError.Log.d(TAG, "Now: " + JoH.dateTimeText(now)); - if (DEBUG_TIMING) UserError.Log.d(TAG, "Last: " + JoH.dateTimeText(last)); - if (DEBUG_TIMING) UserError.Log.d(TAG, "Period: " + String.valueOf(period)); - if (DEBUG_TIMING) UserError.Log.d(TAG, "Interval: " + String.valueOf(interval)); - if (DEBUG_TIMING) UserError.Log.d(TAG, "Grace: " + String.valueOf(grace)); + // refresh should happen before expiration + next = expiry - before; + // add retry interval until future + while (next <= now) { + next += interval; + } + + return next; + + } + + private static long anticipateNextDataPoll(long now, final long last, final long period, final long grace, final long interval) { + + long next; //recent reading (less then data period) => last + period + grace if ((now - last) < period) { next = last + period + grace; - if (DEBUG_TIMING) - UserError.Log.d(TAG, "Recent reading case, next wakeup: " + JoH.dateTimeText(next)); } //old reading => anticipated next + grace else { //last expected - if (DEBUG_TIMING) UserError.Log.d(TAG, "Old reading."); next = now + ((last - now) % period); - if (DEBUG_TIMING) UserError.Log.d(TAG, "Last expected: " + JoH.dateTimeText(next)); //add poll interval until next time is reached - while (next < now) { + while (next <= now) { next += interval; } - if (DEBUG_TIMING) UserError.Log.d(TAG, "Next poll: " + JoH.dateTimeText(next)); //add grace next += grace; - if (DEBUG_TIMING) UserError.Log.d(TAG, "Next poll + grace: " + JoH.dateTimeText(next)); } return next; @@ -156,9 +211,11 @@ public static long anticipateNextWakeUp(long now, final long last, final long pe @Override public int onStartCommand(Intent intent, int flags, int startId) { + final PowerManager.WakeLock wl = JoH.getWakeLock("ConnectFollow-osc", 60000); try { + Logger.Log(TAG, "onStartCommand"); UserError.Log.d(TAG, "WAKE UP WAKE UP WAKE UP"); // Check service should be running @@ -173,30 +230,27 @@ public int onStartCommand(Intent intent, int flags, int startId) { last_wakeup = JoH.tsl(); - // Check current if (gracePeriod == 0) gracePeriod = Pref.getStringToInt("clfollow_grace_period", 30); if (pollInterval == 0) pollInterval = Pref.getStringToInt("clfollow_poll_interval", 5); + if(renewBefore == 0) + renewBefore = 10; + if(renewInterval == 0) + renewInterval = 1; lastBg = BgReading.lastNoSenssor(); if (lastBg != null) { lastBgTime = lastBg.timestamp; } - if (lastBg == null || msSince(lastBg.timestamp) > SAMPLE_PERIOD) { - // Get the data - if (downloader == null) { - downloader = new CareLinkFollowDownloader( - Pref.getString("clfollow_user", ""), - Pref.getString("clfollow_pass", ""), - Pref.getString("clfollow_country", "").toLowerCase(), - Pref.getString("clfollow_patient", "") - ); - } - - if (JoH.ratelimit("last-carelink-follow-poll", 5)) { + // Check if downloader needs to be started (last BG old or token needs to be renewed) + final boolean refreshToken = (JoH.msTill(CareLinkCredentialStore.getInstance().getExpiresOn())< getRenewBeforeMillis()) ? true : false; + final boolean downloadData = (lastBg == null || msSince(lastBg.timestamp) > SAMPLE_PERIOD + getGraceMillis()) ? true : false; + if (refreshToken || downloadData) { + //Only start if rate limit is not exceeded + if (JoH.ratelimit(RATE_LIMIT_NAME, RATE_LIMIT_SAFETY)) { Inevitable.task("CareLink-Follow-Work", 200, () -> { try { - downloader.doEverything(); + getDownloader().doEverything(refreshToken, downloadData); } catch (NullPointerException e) { UserError.Log.e(TAG, "Caught concurrency exception when trying to run doeverything"); } @@ -276,7 +330,7 @@ public static List megaStatus() { //Create status screeen List megaStatus = new ArrayList<>(); megaStatus.add(new StatusItem("Authentication status", authStatus, authHighlight)); - megaStatus.add(new StatusItem("Token expires in", JoH.niceTimeScalar(CareLinkCredentialStore.getInstance().getExpiresIn()))); + megaStatus.add(new StatusItem("Login expires in", JoH.niceTimeScalar(CareLinkCredentialStore.getInstance().getExpiresIn()))); megaStatus.add(new StatusItem()); megaStatus.add(new StatusItem("Latest BG", ageLastBg + (lastBg != null ? " ago" : ""), bgAgeHighlight)); megaStatus.add(new StatusItem("BG receive delay", ageOfBgLastPoll, ageOfLastBgPollHighlight)); @@ -308,6 +362,7 @@ public IBinder onBind(Intent intent) { @Override public void onDestroy() { + Logger.Log(TAG, "onDestroy"); super.onDestroy(); resetInstance(); // manage static reference life cycle } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkAuthenticator.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkAuthenticator.java index bb1e584..b83ed17 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkAuthenticator.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkAuthenticator.java @@ -283,9 +283,12 @@ protected Boolean extractCookies(String url) { } + //Skip cookies if authentication already expired (existing old cookies found) + if (validToDate.getTime() < System.currentTimeMillis()) + return false; + //Update credentials this.credentialStore.setCredential(this.carelinkCountry, authToken, validToDate, cookieList.toArray(new Cookie[0])); - //success return true; } else //error diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkCredentialStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkCredentialStore.java index f37f56b..12a35a2 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkCredentialStore.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkCredentialStore.java @@ -89,6 +89,13 @@ public long getExpiresIn() { return credential.tokenValidTo.getTime() - Calendar.getInstance().getTime().getTime(); } + public long getExpiresOn() { + if (credential == null || credential.tokenValidTo == null) + return -1; + else + return credential.tokenValidTo.getTime(); + } + synchronized void clear() { this.credential = null; PersistentStore.setString(PREF_CARELINK_CREDENTIAL, ""); diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/client/CareLinkClient.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/client/CareLinkClient.java index 111521b..c7a423d 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/client/CareLinkClient.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/client/CareLinkClient.java @@ -677,7 +677,6 @@ protected T getData(HttpUrl url, RequestBody requestBody, Class dataClass // Send request try { - Request request = requestBuilder.build(); response = this.httpClient.newCall(requestBuilder.build()).execute(); this.lastResponseCode = response.code(); if (response.isSuccessful()) { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/utils/Logger.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/utils/Logger.java new file mode 100644 index 0000000..606ed4a --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/utils/Logger.java @@ -0,0 +1,12 @@ +package com.eveningoutpost.dexdrip.cgm.carelinkfollow.utils; + +import com.eveningoutpost.dexdrip.Models.JoH; +import com.eveningoutpost.dexdrip.Models.UserError; + +public class Logger { + + public static void Log(String tag, String text){ + //UserError.Log.e(tag, android.text.format.DateFormat.format("kk:mm:ss ", JoH.tsl()).toString() + text); + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/Preferences.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/Preferences.java index 2dbd2f8..4ef8898 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utils/Preferences.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/Preferences.java @@ -1230,12 +1230,12 @@ public void run() { else { CareLinkAuthenticator authenticator = new CareLinkAuthenticator(country, CareLinkCredentialStore.getInstance()); if (authenticator.authenticate(getActivity())) { - JoH.static_toast(preference.getContext(), "Login completed!", Toast.LENGTH_LONG); + JoH.static_toast(preference.getContext(), "Authenticated!", Toast.LENGTH_LONG); CareLinkFollowService.resetInstanceAndInvalidateSession(); CollectionServiceStarter.restartCollectionServiceBackground(); } else - JoH.static_toast(preference.getContext(), "Login failed!", Toast.LENGTH_LONG); + JoH.static_toast(preference.getContext(), "Not authenticated!", Toast.LENGTH_LONG); } } catch (InterruptedException e) { diff --git a/wear/build.gradle b/wear/build.gradle index 439c1c8..1d04249 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -9,7 +9,7 @@ android { // abortOnError false } defaultConfig { - applicationId "com.eveningoutpost.dexdrip" + applicationId "com.eveningoutpost.dexdripva4" minSdkVersion 21 //noinspection ExpiredTargetSdkVersion targetSdkVersion 23