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 c5a98d7463..13bdbd6058 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 @@ -2,6 +2,8 @@ import android.os.PowerManager; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkAuthenticator; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkCredentialStore; import com.eveningoutpost.dexdrip.models.JoH; import com.eveningoutpost.dexdrip.models.UserError; import com.eveningoutpost.dexdrip.utilitymodels.CollectionServiceStarter; @@ -47,6 +49,7 @@ public String getStatus() { this.carelinkCountry = carelinkCountry; this.carelinkPatient = carelinkPatient; loginDataLooksOkay = !emptyString(carelinkUsername) && !emptyString(carelinkPassword) && carelinkCountry != null && !emptyString(carelinkCountry); + loginDataLooksOkay = true; } public static void resetInstance() { @@ -54,43 +57,63 @@ public static void resetInstance() { CollectionServiceStarter.restartCollectionServiceBackground(); } - public boolean doEverything() { - msg("Start download"); + public void doEverything(boolean refreshToken, boolean downloadData) { + if (refreshToken) + this.refreshToken(); + if (downloadData) + this.downloadData(); + } - if (D) UserError.Log.e(TAG, "doEverything called"); - if (loginDataLooksOkay) { + private void downloadData() { + msg("Start download"); + if (checkCredentials()) { try { if (getCareLinkClient() != null) { extendWakeLock(30_000); backgroundProcessConnectData(); } else { - UserError.Log.d(TAG, "Cannot get data as ConnectClient is null"); - return false; + UserError.Log.d(TAG, "Cannot get data as CareLinkClient is null"); + msg("Download data failed!"); } - return true; } catch (Exception e) { UserError.Log.e(TAG, "Got exception in getData() " + e); releaseWakeLock(); - return false; + msg("Download data failed!"); } - } else { - final String invalid = "Invalid CareLink login data!"; - msg(invalid); - UserError.Log.e(TAG, invalid); - if (emptyString(carelinkUsername)) { - UserError.Log.e(TAG, "CareLink Username empty!"); - } - if (emptyString(carelinkPassword)) { - UserError.Log.e(TAG, "CareLink Password empty!"); - } - if (carelinkCountry == null) { - UserError.Log.e(TAG, "CareLink Country empty!"); - } else if (!CountryUtils.isSupportedCountry(carelinkCountry)) { - UserError.Log.e(TAG, "CareLink Country not supported!"); + } + } + + private void refreshToken() { + msg("Start refreshing token"); + if (checkCredentials()) { + try { + if (new CareLinkAuthenticator(CareLinkCredentialStore.getInstance().getCredential().country, CareLinkCredentialStore.getInstance()).refreshToken()) { + UserError.Log.d(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!"); } - return false; } + } + 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) { @@ -118,64 +141,44 @@ private void processConnectData() { carelinkClient = getCareLinkClient(); //Get RecentData from CareLink client if (carelinkClient != null) { + //Get data + try { + if (JoH.emptyString(this.carelinkPatient)) + recentData = getCareLinkClient().getRecentData(); + else + recentData = getCareLinkClient().getRecentData(this.carelinkPatient); + lastResponseCode = carelinkClient.getLastResponseCode(); + } catch (Exception e) { + UserError.Log.e(TAG, "Exception in CareLink data download: " + e); + } - //Try twice in case of 401 error - for (int i = 0; i < 2; i++) { - - //Get data + //Process data + if (recentData != null) { + UserError.Log.d(TAG, "Success get data!"); try { - if (JoH.emptyString(this.carelinkPatient)) - recentData = getCareLinkClient().getRecentData(); - else - recentData = getCareLinkClient().getRecentData(this.carelinkPatient); - lastResponseCode = carelinkClient.getLastResponseCode(); + UserError.Log.d(TAG, "Start process data"); + //Process CareLink data (conversion and update xDrip data) + CareLinkDataProcessor.processData(recentData, true); + UserError.Log.d(TAG, "ProcessData finished!"); + //Update Service status + CareLinkFollowService.updateBgReceiveDelay(); + msg(null); } catch (Exception e) { - UserError.Log.e(TAG, "Exception in CareLink data download: " + e); + UserError.Log.e(TAG, "Exception in data processing: " + e); + msg("Data processing error!"); } - - //Process data - if (recentData != null) { - UserError.Log.d(TAG, "Success get data!"); - try { - UserError.Log.d(TAG, "Start process data"); - //Process CareLink data (conversion and update xDrip data) - CareLinkDataProcessor.processData(recentData, true); - UserError.Log.d(TAG, "ProcessData finished!"); - //Update Service status - CareLinkFollowService.updateBgReceiveDelay(); - msg(null); - } catch (Exception e) { - UserError.Log.e(TAG, "Exception in data processing: " + e); - msg("Data processing error!"); - } - //Data receive error + //Data receive error + } else { + if (carelinkClient.getLastResponseCode() == 401) { + UserError.Log.e(TAG, "CareLink login error! Response code: " + carelinkClient.getLastResponseCode()); + msg("Login error!"); + //login 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) { - 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!"); - } + UserError.Log.e(TAG, "CareLink download error! Response code: " + carelinkClient.getLastResponseCode()); + UserError.Log.e(TAG, "Error message: " + getCareLinkClient().getLastErrorMessage()); + msg("Download data failed!"); } - - //Next try only for 401 error and first attempt - if (!(carelinkClient.getLastResponseCode() == 401 && i == 0)) - break; - } - } } @@ -185,7 +188,8 @@ private CareLinkClient getCareLinkClient() { if (careLinkClient == null) { try { UserError.Log.d(TAG, "Creating CareLinkClient"); - careLinkClient = new CareLinkClient(carelinkUsername, carelinkPassword, carelinkCountry); + if (CareLinkCredentialStore.getInstance().getAuthStatus() == CareLinkCredentialStore.AUTHENTICATED) + careLinkClient = new CareLinkClient(CareLinkCredentialStore.getInstance()); } catch (Exception e) { UserError.Log.e(TAG, "Error creating CareLinkClient", e); } 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 c2c99c209c..4667501148 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 @@ -6,6 +6,7 @@ import android.support.annotation.Nullable; import android.text.SpannableString; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkCredentialStore; import com.eveningoutpost.dexdrip.models.BgReading; import com.eveningoutpost.dexdrip.models.JoH; import com.eveningoutpost.dexdrip.models.UserError; @@ -37,6 +38,9 @@ public class CareLinkFollowService extends ForegroundService { private static final String TAG = "CareLinkFollow"; private static final long SAMPLE_PERIOD = DEXCOM_PERIOD; + 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 = ""; @@ -51,6 +55,8 @@ public class CareLinkFollowService extends ForegroundService { private static CareLinkFollowDownloader downloader; private static volatile int gracePeriod = 0; private static volatile int missedPollInterval = 0; + private static volatile int renewBefore = 0; + private static volatile int renewInterval = 0; @Override @@ -86,6 +92,8 @@ public static void resetInstance() { downloader = null; gracePeriod = 0; missedPollInterval = 0; + renewBefore = 0; + renewInterval = 0; } private static boolean shouldServiceRun() { @@ -103,18 +111,60 @@ private static long getMissedIntervalMillis() { return Constants.MINUTE_IN_MS * missedPollInterval; } + private static long getRenewBeforeMillis() { + return Constants.MINUTE_IN_MS * renewBefore; + } + + private static long getRenewIntervalMillis() { + return Constants.MINUTE_IN_MS * renewInterval; + } + static void 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(), getMissedIntervalMillis()); + final long nextTokenRefresh = anticipateNextTokenRefresh(JoH.tsl(), CareLinkCredentialStore.getInstance().getExpiresOn(), getRenewBeforeMillis(), getRenewIntervalMillis()); + final long nextDataPoll = anticipateNextDataPoll(JoH.tsl(), last, SAMPLE_PERIOD, getGraceMillis(), getMissedIntervalMillis()); + + // 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); + wakeup_time = next; - UserError.Log.d(TAG, "Anticipate next: " + JoH.dateTimeText(next) + " last BG timestamp: " + JoH.dateTimeText(last)); + UserError.Log.d(TAG, "Anticipate next: " + JoH.dateTimeText(next) + scheduleReason + JoH.dateTimeText(last)); JoH.wakeUpIntent(xdrip.getAppContext(), JoH.msTill(next), WakeLockTrampoline.getPendingIntent(CareLinkFollowService.class, Constants.CARELINK_SERVICE_FAILOVER_ID)); } - public static long anticipateNextWakeUp(long now, final long last, final long period, final long grace, final long missedInterval) { + private static long anticipateNextTokenRefresh(long now, final long expiry, final long before, final long interval){ + + long next; + + // refresh should happen before expiration + next = expiry - before; + // add retry interval until future + while (next <= now) { + next += interval; + } + + return next; + + } + + public static long anticipateNextDataPoll(long now, final long last, final long period, final long grace, final long missedInterval) { long next; @@ -127,7 +177,7 @@ public static long anticipateNextWakeUp(long now, final long last, final long pe //last expected next = now + ((last - now) % period); //add missed poll interval until future time is reached - while (next < now) { + while (next <= now) { next += missedInterval; } //add grace @@ -138,6 +188,18 @@ public static long anticipateNextWakeUp(long now, final long last, final long pe } + 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; + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { final PowerManager.WakeLock wl = JoH.getWakeLock("CareLinkFollow-osc", 60000); @@ -161,25 +223,23 @@ public int onStartCommand(Intent intent, int flags, int startId) { gracePeriod = Pref.getStringToInt("clfollow_grace_period", 30); if (missedPollInterval == 0) missedPollInterval = Pref.getStringToInt("clfollow_missed_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"); } @@ -202,7 +262,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { public static List megaStatus() { final BgReading lastBg = BgReading.lastNoSenssor(); - long hightlightGrace = Constants.SECOND_IN_MS * 30; // 30 seconds + long hightlightGrace = Constants.SECOND_IN_MS * getGraceMillis() + Constants.SECOND_IN_MS * 10; //garce + 20 seconds for processing // Status for BG receive delay (time from bg was recorded till received in xdrip) String ageOfBgLastPoll = "n/a"; @@ -228,8 +288,31 @@ public static List megaStatus() { } } + // Status of authentication + String authStatus = null; + StatusItem.Highlight authHighlight = null; + switch (CareLinkCredentialStore.getInstance().getAuthStatus()) { + case CareLinkCredentialStore.NOT_AUTHENTICATED: + authStatus = "NOT AUTHENTICATED"; + authHighlight = StatusItem.Highlight.CRITICAL; + break; + case CareLinkCredentialStore.AUTHENTICATED: + authHighlight = StatusItem.Highlight.GOOD; + authStatus = "AUTHENTICATED"; + break; + case CareLinkCredentialStore.TOKEN_EXPIRED: + authHighlight = StatusItem.Highlight.BAD; + authStatus = "TOKEN EXPIRED"; + break; + } + + + //Build status screeen List megaStatus = new ArrayList<>(); + megaStatus.add(new StatusItem("Authentication status", authStatus, authHighlight)); + 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)); megaStatus.add(new StatusItem("Data period:", JoH.niceTimeScalar(SAMPLE_PERIOD))); 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 new file mode 100644 index 0000000000..69dcaf5bc8 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkAuthenticator.java @@ -0,0 +1,303 @@ +package com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.os.Handler; +import android.os.Looper; +import android.support.v7.widget.LinearLayoutCompat; +import android.view.ViewGroup; +import android.webkit.CookieManager; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.concurrent.Semaphore; + +import okhttp3.Cookie; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class CareLinkAuthenticator { + + protected static final String CARELINK_CONNECT_SERVER_EU = "carelink.minimed.eu"; + protected static final String CARELINK_CONNECT_SERVER_US = "carelink.minimed.com"; + protected static final String CARELINK_LANGUAGE_EN = "en"; + protected static final String CARELINK_AUTH_TOKEN_COOKIE_NAME = "auth_tmp_token"; + protected static final String CARELINK_TOKEN_VALIDTO_COOKIE_NAME = "c_token_valid_to"; + + protected static final SimpleDateFormat[] VALIDTO_DATE_FORMATS = { + new SimpleDateFormat("EEE MMM d HH:mm:ss zzz yyyy", Locale.ENGLISH), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ENGLISH), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz", Locale.ENGLISH), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ENGLISH), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH), + //new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.ENGLISH), + //new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.ENGLISH), + new SimpleDateFormat("EEE MMM d HH:mm:ss zzz yyyy"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"), + //new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"), + //new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX") + }; + + + private final Semaphore available = new Semaphore(0, true); + private String carelinkCountry; + private CareLinkCredentialStore credentialStore; + + + public CareLinkAuthenticator(String carelinkCountry, CareLinkCredentialStore credentialStore) { + this.carelinkCountry = carelinkCountry; + this.credentialStore = credentialStore; + } + + /* + public synchronized CareLinkCredential getCreditential() throws InterruptedException { + if(Looper.myLooper() == Looper.getMainLooper()) + throw new RuntimeException("don't call getAccessToken() from the main thread."); + + switch (credentialStore.getAuthStatus()) { + case CareLinkCredentialStore.NOT_AUTHENTICATED: + authenticate(); + available.acquire(); + break; + case CareLinkCredentialStore.TOKEN_EXPIRED: + refreshToken(); + available.acquire(); + break; + case CareLinkCredentialStore.AUTHENTICATED: + break; + } + + return credentialStore.getCredential(); + } + */ + + public boolean authenticate(Activity context) throws InterruptedException { + if (Looper.myLooper() == Looper.getMainLooper()) + throw new RuntimeException("don't call authenticate() from the main thread."); + + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + showDialog(context); + } + }); + available.acquire(); + return (credentialStore.getAuthStatus() == CareLinkCredentialStore.AUTHENTICATED); + } + + public boolean refreshToken() { + //If not authenticated => unable to refresh + if (credentialStore.getAuthStatus() == CareLinkCredentialStore.NOT_AUTHENTICATED) + return false; + + HttpUrl url = null; + OkHttpClient httpClient = null; + Request.Builder requestBuilder = null; + Response response = null; + EditableCookieJar cookieJar = null; + + + //Build client with cookies from CredentialStore + cookieJar = new EditableCookieJar(); + httpClient = new OkHttpClient.Builder() + .cookieJar(cookieJar) + .build(); + cookieJar.AddCookies(credentialStore.getCredential().cookies); + + //Build request + url = new HttpUrl.Builder() + .scheme("https") + .host(this.careLinkServer()) + .addPathSegments("patient/sso/reauth") + .build(); + requestBuilder = new Request.Builder() + .url(url) + .post(RequestBody.create(null, new byte[0])) + .addHeader("Accept", "application/json, text/plain, */*") + .addHeader("Accept-Language", "en;q=0.9, *;q=0.8") + .addHeader("Connection", "keep-alive") + .addHeader("Authorization", credentialStore.getCredential().getAuthorizationFieldValue()) + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36") + .addHeader("Sec-Ch-Ua", "\"Google Chrome\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\""); + + //Send request to refresh token + try { + response = httpClient.newCall(requestBuilder.build()).execute(); + //successful response + if (response.isSuccessful()) { + //New authentication cookies found + if (cookieJar.contains(CARELINK_AUTH_TOKEN_COOKIE_NAME) && cookieJar.contains(CARELINK_TOKEN_VALIDTO_COOKIE_NAME)) { + //Update credentials + this.credentialStore.setCredential( + this.carelinkCountry, + cookieJar.getCookies(CARELINK_AUTH_TOKEN_COOKIE_NAME).get(0).value(), + this.parseValidTo(cookieJar.getCookies(CARELINK_TOKEN_VALIDTO_COOKIE_NAME).get(0).value()), + cookieJar.getAllCookies().toArray(new Cookie[0])); + } else { + return false; + } + } + //error in response + else { + return false; + } + response.close(); + } catch (IOException e) { + return false; + } finally { + + } + + return (credentialStore.getAuthStatus() == CareLinkCredentialStore.AUTHENTICATED); + + } + + private void showDialog(Activity context) { + + //Create dialog + final Dialog authDialog = new Dialog(context); + LinearLayoutCompat layout = new LinearLayoutCompat(authDialog.getContext()); + WebView webView = new WebView(authDialog.getContext()); + webView.setLayoutParams(new LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + layout.addView(webView); + authDialog.setContentView(layout); + + authDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + unlock(); + } + }); + + //Configure Webview + webView.getSettings().setJavaScriptEnabled(true); + webView.getSettings().setUserAgentString("Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36"); + HashMap headers = new HashMap<>(); + headers.put("Accept-Language", "en;q=0.9, *;q=0.8"); + headers.put("Sec-Ch-Ua", "\"Google Chrome\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\""); + webView.loadUrl(this.getLoginUrl(), headers); + webView.setWebViewClient(new WebViewClient() { + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + if (CareLinkAuthenticator.this.extractCookies(url)) + authDialog.dismiss(); + } + + }); + + + //Set dialog display infos and show it + authDialog.setCancelable(true); + authDialog.getWindow().setLayout(LinearLayoutCompat.LayoutParams.MATCH_PARENT, LinearLayoutCompat.LayoutParams.MATCH_PARENT); + authDialog.show(); + } + + protected String getLoginUrl() { + + HttpUrl url = null; + + url = new HttpUrl.Builder() + .scheme("https") + .host(this.careLinkServer()) + .addPathSegments("patient/sso/login") + .addQueryParameter("country", this.carelinkCountry) + .addQueryParameter("lang", CARELINK_LANGUAGE_EN) + .build(); + + return url.toString(); + + } + + protected String careLinkServer() { + if (this.carelinkCountry.equals("us")) + return CARELINK_CONNECT_SERVER_US; + else + return CARELINK_CONNECT_SERVER_EU; + } + + protected Boolean extractCookies(String url) { + String cookies = null; + String authToken = null; + String host = null; + Date validToDate = null; + ArrayList cookieList; + + + cookies = CookieManager.getInstance().getCookie(url); + + //Authentication cookies are present + if (cookies != null && cookies.contains(CARELINK_AUTH_TOKEN_COOKIE_NAME) && cookies.contains(CARELINK_TOKEN_VALIDTO_COOKIE_NAME)) { + + //Build cookies + host = HttpUrl.parse(url).host(); + cookieList = new ArrayList(); + + for (String cookie : cookies.split("; ")) { + + String[] cookieParts = cookie.split("="); + + Cookie.Builder cookieBuilder = new Cookie.Builder() + .name(cookieParts[0]) + .value(cookieParts[1]) + .path("/") + .domain(host); + + if (cookieParts[0].contains(CARELINK_AUTH_TOKEN_COOKIE_NAME)) { + cookieBuilder.secure(); + authToken = cookieParts[1]; + } + + if (cookieParts[0].contains(CARELINK_TOKEN_VALIDTO_COOKIE_NAME)) { + validToDate = this.parseValidTo(cookieParts[1]); + cookieBuilder.secure(); + } + + cookieList.add(cookieBuilder.build()); + + } + + //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 + return false; + } + + protected void unlock() { + if (available.availablePermits() <= 0) + available.release(); + } + + protected Date parseValidTo(String validToDateString) { + for (SimpleDateFormat zonedFormat : VALIDTO_DATE_FORMATS) { + try { + return zonedFormat.parse(validToDateString); + } catch (Exception ex) { + } + } + return null; + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkCredential.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkCredential.java new file mode 100644 index 0000000000..352374f257 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkCredential.java @@ -0,0 +1,33 @@ +package com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth; + +import java.util.Calendar; +import java.util.Date; + +import okhttp3.Cookie; + +public class CareLinkCredential { + + public String country = null; + public String accessToken = null; + public Cookie[] cookies = null; + public Date tokenValidTo = null; + + public String getToken() { + return accessToken; + } + + public String getAuthorizationFieldValue() { + if (this.getToken() == null) + return null; + else + return "Bearer " + this.getToken(); + } + + public long getExpiresIn() { + if (this.tokenValidTo == null) + return -1; + else + return this.tokenValidTo.getTime() - Calendar.getInstance().getTime().getTime(); + } + +} 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 new file mode 100644 index 0000000000..ceadeabb97 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/CareLinkCredentialStore.java @@ -0,0 +1,113 @@ +package com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth; + + +import com.eveningoutpost.dexdrip.models.UserError; +import com.eveningoutpost.dexdrip.utilitymodels.PersistentStore; +import com.google.gson.GsonBuilder; + +import java.util.Calendar; +import java.util.Date; + +import okhttp3.Cookie; + +public class CareLinkCredentialStore { + + private static final String TAG = "CareLinkCredentialStore"; + + public final static int NOT_AUTHENTICATED = 0; + public final static int TOKEN_EXPIRED = 1; + public final static int AUTHENTICATED = 2; + + private CareLinkCredential credential = null; + private static CareLinkCredentialStore instance = null; + private final static String PREF_CARELINK_CREDENTIAL = "carelink_credential"; + + private int authStatus = NOT_AUTHENTICATED; + + + private CareLinkCredentialStore() { + + } + + public static CareLinkCredentialStore getInstance() { + if (instance == null) { + instance = new CareLinkCredentialStore(); + UserError.Log.d(TAG, "Trying to restore saved Credential"); + String credJson = PersistentStore.getString(PREF_CARELINK_CREDENTIAL, ""); + if (!credJson.equals("")) { + try { + CareLinkCredential savedCred = new GsonBuilder().create().fromJson(credJson, CareLinkCredential.class); + instance.setCredential(savedCred.country, savedCred.accessToken, savedCred.tokenValidTo, savedCred.cookies, false); + } catch (Exception e) { + UserError.Log.d(TAG, "Error when restoring saved Credential: " + e.getMessage()); + } + } else { + UserError.Log.d(TAG, "No saved Credential found!"); + } + + } + return instance; + } + + synchronized void setCredential(String country, String accessToken, Date tokenValidTo, Cookie[] cookies) { + this.setCredential(country, accessToken, tokenValidTo, cookies, true); + } + + protected synchronized void setCredential(String country, String accessToken, Date tokenValidTo, Cookie[] cookies, boolean save) { + credential = new CareLinkCredential(); + credential.country = country; + credential.accessToken = accessToken; + credential.cookies = cookies; + credential.tokenValidTo = tokenValidTo; + if (credential.accessToken == null || credential.tokenValidTo == null) + authStatus = NOT_AUTHENTICATED; + else + evaluateExpiration(); + UserError.Log.d(TAG, "Credential updated!"); + if (save) { + try { + PersistentStore.setString(PREF_CARELINK_CREDENTIAL, new GsonBuilder().create().toJson(credential)); + UserError.Log.d(TAG, "Credential saved!"); + } catch (Exception e) { + UserError.Log.d(TAG, "Error saving Credential: " + e.getMessage()); + } + } + } + + public CareLinkCredential getCredential() { + return credential; + } + + public int getAuthStatus() { + return authStatus; + } + + public long getExpiresIn() { + if (credential == null || credential.tokenValidTo == null) + return -1; + else + 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, ""); + UserError.Log.d(TAG, "Credential cleared"); + authStatus = NOT_AUTHENTICATED; + } + + protected void evaluateExpiration() { + if (this.getExpiresIn() < 0) + authStatus = TOKEN_EXPIRED; + else + authStatus = AUTHENTICATED; + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/EditableCookieJar.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/EditableCookieJar.java new file mode 100644 index 0000000000..5f06c6548b --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/carelinkfollow/auth/EditableCookieJar.java @@ -0,0 +1,100 @@ +package com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; + +public class EditableCookieJar implements CookieJar { + + private List storage = new ArrayList<>(); + + @Override + public void saveFromResponse(HttpUrl url, List cookies) { + //add cookies, delete cookie with same name before add + for (Cookie cookie : cookies) { + if (this.contains(cookie.name())) + this.deleteCookie(cookie.name()); + this.storage.add(cookie); + } + } + + @Override + public List loadForRequest(HttpUrl url) { + + List cookies = new ArrayList<>(); + + // Remove expired Cookies + //removeExpiredCookies(); + + // Only return matching Cookies + for (Cookie cookie : storage) { + if (cookie.matches(url)) { + cookies.add(cookie); + } + } + + return cookies; + } + + public List getCookies(String name) { + + List cookies = new ArrayList<>(); + + // Remove expired Cookies + //removeExpiredCookies(); + + // Only return matching Cookies + for (Cookie cookie : storage) { + if (cookie.name().equals(name)) { + cookies.add(cookie); + } + } + + return cookies; + + } + + public List getAllCookies() { + return storage; + } + + public void AddCookie(Cookie cookie) { + storage.add(cookie); + } + + public void AddCookies(Cookie[] cookies) { + storage.addAll(Arrays.asList(cookies)); + } + + public boolean contains(String name) { + return (getCookies(name).size() > 0); + } + + public void deleteCookie(String name) { + for (int i = 0; i < storage.size(); i++) { + if (storage.get(i).name().contains(name)) { + storage.remove(i); + } + } + } + + public void deleteAllCookies() { + storage.clear(); + } + + /* + private void removeExpiredCookies() { + for (int i = 0; i < storage.size(); i++) { + if (storage.get(i).expiresAt() < System.currentTimeMillis()) { + storage.remove(i); + } + } + } + + */ + +} 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 ed6341028b..56764025bc 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 @@ -1,5 +1,7 @@ package com.eveningoutpost.dexdrip.cgm.carelinkfollow.client; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkCredentialStore; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.EditableCookieJar; import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.ActiveNotification; import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.ClearedNotification; import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.CountrySettings; @@ -24,9 +26,11 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import okhttp3.ConnectionPool; import okhttp3.FormBody; import okhttp3.HttpUrl; import okhttp3.MediaType; @@ -60,8 +64,10 @@ public class CareLinkClient { new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX") }; + //Communication info protected OkHttpClient httpClient = null; protected boolean loginInProcess = false; + protected boolean collectingSessionInfos = false; protected int lastResponseCode; public int getLastResponseCode() { @@ -92,7 +98,11 @@ public String getLastStackTraceString() { return lastStackTraceString; } + //Credentials + protected CareLinkCredentialStore credentialStore; + //Session info + protected boolean sessionInfosLoaded = false; protected User sessionUser; public User getSessionUser() { @@ -155,6 +165,25 @@ public CareLinkClient(String carelinkUsername, String carelinkPassword, String c } + public CareLinkClient(CareLinkCredentialStore credentialStore) { + this.carelinkCountry = credentialStore.getCredential().country; + this.credentialStore = credentialStore; + this.createHttpClient(); + } + + private void createHttpClient(){ + + EditableCookieJar cookieJar = null; + + cookieJar = new EditableCookieJar(); + cookieJar.AddCookies(this.credentialStore.getCredential().cookies); + + this.httpClient = new OkHttpClient.Builder() + .cookieJar(cookieJar) + .connectionPool(new ConnectionPool(5, 10, TimeUnit.MINUTES)) + .build(); + } + protected String careLinkServer() { if (this.carelinkCountry.equals("us")) return CARELINK_CONNECT_SERVER_US; @@ -276,14 +305,8 @@ protected boolean executeLoginProcedure() { lastErrorMessage = ""; try { - //Clear cookies - ((SimpleOkHttpCookieJar) this.httpClient.cookieJar()).deleteAllCookies(); - - // Clear basic infos - this.sessionUser = null; - this.sessionProfile = null; - this.sessionCountrySettings = null; - this.sessionMonitorData = null; + //Clear session + this.clearSessionInfos(); //Open login (get SessionId and SessionData) loginSessionResponse = this.getLoginSession(); @@ -301,6 +324,26 @@ protected boolean executeLoginProcedure() { consentResponse.close(); // Get required sessions infos + if(this.getSessionInfos()) + lastLoginSuccess = true; + + } catch (Exception e) { + lastErrorMessage = e.getClass().getSimpleName() + ":" + e.getMessage(); + // lastStackTraceString = Log.getStackTraceString(e); + } finally { + loginInProcess = false; + } + + return lastLoginSuccess; + + } + + protected boolean getSessionInfos(){ + + collectingSessionInfos = true; + + try { + this.clearSessionInfos(); // User this.sessionUser = this.getMyUser(); // Profile @@ -308,9 +351,8 @@ protected boolean executeLoginProcedure() { // Country settings this.sessionCountrySettings = this.getMyCountrySettings(); // Recent uploads (only for patients) - if(!this.sessionUser.isCarePartner()) + if (!this.sessionUser.isCarePartner()) this.sessionRecentUploads = this.getRecentUploads(30); - // Multi follow enabled on server this.sessionM2MEnabled = this.getM2MEnabled().value; // Multi follow + Care Partner => patients if (this.sessionM2MEnabled && this.sessionUser.isCarePartner()) @@ -319,34 +361,33 @@ protected boolean executeLoginProcedure() { else this.sessionMonitorData = this.getMonitorData(); - } catch (Exception e) { - lastErrorMessage = e.getClass().getSimpleName() + ":" + e.getMessage(); - // lastStackTraceString = Log.getStackTraceString(e); + if (this.sessionUser != null && this.sessionProfile != null && this.sessionCountrySettings != null && this.sessionM2MEnabled != null && + (((!this.sessionM2MEnabled || !this.sessionUser.isCarePartner()) && this.sessionMonitorData != null) || + (this.sessionM2MEnabled && this.sessionUser.isCarePartner() && this.sessionPatients != null))) + this.sessionInfosLoaded = true; + else { + this.clearSessionInfos(); + } + } catch (Exception ex) { + } finally { - loginInProcess = false; + collectingSessionInfos = false; } - // Set login success if everything was ok: - if (this.sessionUser != null && this.sessionProfile != null && this.sessionCountrySettings != null && this.sessionM2MEnabled != null && - (((!this.sessionM2MEnabled || !this.sessionUser.isCarePartner()) && this.sessionMonitorData != null) || - (this.sessionM2MEnabled && this.sessionUser.isCarePartner() && this.sessionPatients != null))) - lastLoginSuccess = true; - //Clear cookies if error occured during logon - else - this.clearSessionInfos(); - return lastLoginSuccess; + return this.sessionInfosLoaded; } protected void clearSessionInfos() { - ((SimpleOkHttpCookieJar) this.httpClient.cookieJar()).deleteAllCookies(); + //((SimpleOkHttpCookieJar) this.httpClient.cookieJar()).deleteAllCookies(); this.sessionUser = null; this.sessionProfile = null; this.sessionCountrySettings = null; this.sessionMonitorData = null; this.sessionPatients = null; + this.sessionInfosLoaded = false; } protected Response getLoginSession() throws IOException { @@ -455,25 +496,37 @@ protected String extractResponseData(String respBody, String groupRegex, int gro protected String getAuthorizationToken() { + // CredentialStore is used + if(this.credentialStore != null){ + if(!this.sessionInfosLoaded && this.credentialStore.getAuthStatus() == CareLinkCredentialStore.AUTHENTICATED && !this.collectingSessionInfos) + { + this.getSessionInfos(); + } + if(!this.collectingSessionInfos && !this.sessionInfosLoaded) + return null; + else + return this.credentialStore.getCredential().getAuthorizationFieldValue(); // New token is needed: // a) no token or about to expire => execute authentication // b) last response 401 - if (!((SimpleOkHttpCookieJar) httpClient.cookieJar()).contains(CARELINK_AUTH_TOKEN_COOKIE_NAME) - || !((SimpleOkHttpCookieJar) httpClient.cookieJar()).contains(CARELINK_TOKEN_VALIDTO_COOKIE_NAME) - || !((new Date(Date.parse(((SimpleOkHttpCookieJar) httpClient.cookieJar()) - .getCookies(CARELINK_TOKEN_VALIDTO_COOKIE_NAME).get(0).value()))) - .after(new Date(new Date(System.currentTimeMillis()).getTime() - + AUTH_EXPIRE_DEADLINE_MINUTES * 60000))) - || this.lastResponseCode == 401 - || (!loginInProcess && !this.lastLoginSuccess) - ) { - //execute new login process - if (this.loginInProcess || !this.executeLoginProcedure()) - return null; - } + } else { + if (!((SimpleOkHttpCookieJar) httpClient.cookieJar()).contains(CARELINK_AUTH_TOKEN_COOKIE_NAME) + || !((SimpleOkHttpCookieJar) httpClient.cookieJar()).contains(CARELINK_TOKEN_VALIDTO_COOKIE_NAME) + || !((new Date(Date.parse(((SimpleOkHttpCookieJar) httpClient.cookieJar()) + .getCookies(CARELINK_TOKEN_VALIDTO_COOKIE_NAME).get(0).value()))) + .after(new Date(new Date(System.currentTimeMillis()).getTime() + + AUTH_EXPIRE_DEADLINE_MINUTES * 60000))) + || this.lastResponseCode == 401 + || (!loginInProcess && !this.lastLoginSuccess) + ) { + //execute new login process + if (this.loginInProcess || !this.executeLoginProcedure()) + return null; + } - //there can be only one - return "Bearer" + " " + ((SimpleOkHttpCookieJar) httpClient.cookieJar()).getCookies(CARELINK_AUTH_TOKEN_COOKIE_NAME).get(0).value(); + //there can be only one + return "Bearer" + " " + ((SimpleOkHttpCookieJar) httpClient.cookieJar()).getCookies(CARELINK_AUTH_TOKEN_COOKIE_NAME).get(0).value(); + } } @@ -688,8 +741,9 @@ protected void addHttpHeaders(Request.Builder requestBuilder, RequestType type) //Add common browser headers requestBuilder .addHeader("Accept-Language", "en;q=0.9, *;q=0.8") - .addHeader("sec-ch-ua", "\"Chromium\";v=\"115\", \"Google Chrome\";v=\"115\", \"Not:A-Brand\";v=\"99\"") - .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"); + .addHeader("Connection", "keep-alive") + .addHeader("Sec-Ch-Ua", "\"Google Chrome\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"") + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36"); //Set media type based on request type switch (type) { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SourceWizard.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SourceWizard.java index 9ed23d8cbb..937fcc3eab 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SourceWizard.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SourceWizard.java @@ -73,7 +73,6 @@ public class SourceWizard { other.addChild(new Item("Dex Share Follower", DexCollectionType.SHFollow, R.drawable.nsfollow_icon)); // other.addChild(new Item("EverSense", DexCollectionType.NSEmulator, R.drawable.wikimedia_eversense_icon_pbroks13)); - other.addChild(new Item("CareLink Follower", DexCollectionType.CLFollow, R.drawable.nsfollow_icon)); } } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/DexCollectionHelper.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/DexCollectionHelper.java index bcb09b905a..87c2678e72 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utils/DexCollectionHelper.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/DexCollectionHelper.java @@ -137,6 +137,7 @@ public void run() { Home.staticRefreshBGChartsOnIdle(); break; + /* LOGIN via browser is required currently case CLFollow: textSettingDialog(activity, "clfollow_country", "CareLink Country", @@ -179,6 +180,8 @@ public void run() { }); break; + */ + // TODO G4 Share Receiver // TODO Parakeet / Wifi ?? 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 2f8ac81970..493ecf412d 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utils/Preferences.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/Preferences.java @@ -61,6 +61,8 @@ import com.eveningoutpost.dexdrip.cgm.nsfollow.NightscoutFollow; import com.eveningoutpost.dexdrip.cgm.sharefollow.ShareFollowService; import com.eveningoutpost.dexdrip.cgm.webfollow.Cpref; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkAuthenticator; +import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkCredentialStore; import com.eveningoutpost.dexdrip.g5model.DexSyncKeeper; import com.eveningoutpost.dexdrip.g5model.Ob1G5StateMachine; import com.eveningoutpost.dexdrip.healthconnect.HealthConnectEntry; @@ -1305,10 +1307,11 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } //CareLink Follow preferences - final Preference carelinkFollowUser = findPreference("clfollow_user"); - final Preference carelinkFollowPass = findPreference("clfollow_pass"); + //final Preference carelinkFollowUser = findPreference("clfollow_user"); + //final Preference carelinkFollowPass = findPreference("clfollow_pass"); final Preference carelinkFollowCountry = findPreference("clfollow_country"); final Preference carelinkFollowPatient = findPreference("clfollow_patient"); + final Preference carelinkFollowLogin = findPreference("clfollow_login"); final Preference carelinkFollowGracePeriod = findPreference("clfollow_grace_period"); final Preference carelinkFollowMissedPollInterval = findPreference("clfollow_missed_poll_interval"); final Preference carelinkFollowDownloadFingerBGs = findPreference("clfollow_download_finger_bgs"); @@ -1318,10 +1321,11 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { //Add CL prefs for CLFollower if (collectionType == DexCollectionType.CLFollow) { //Add CL prefs - collectionCategory.addPreference(carelinkFollowUser); - collectionCategory.addPreference(carelinkFollowPass); + //collectionCategory.addPreference(carelinkFollowUser); + //collectionCategory.addPreference(carelinkFollowPass); collectionCategory.addPreference(carelinkFollowCountry); collectionCategory.addPreference(carelinkFollowPatient); + collectionCategory.addPreference(carelinkFollowLogin); collectionCategory.addPreference(carelinkFollowGracePeriod); collectionCategory.addPreference(carelinkFollowMissedPollInterval); collectionCategory.addPreference(carelinkFollowDownloadFingerBGs); @@ -1337,24 +1341,55 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { return true; } }; - //Register prefChange handler + //Pref click handler for Login + final Preference.OnPreferenceClickListener carelinkLoginListener = new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + new Thread() { + public void run() { + try { + String country = Pref.getString("clfollow_country", "").toLowerCase(); + if (country.equals("")) + JoH.static_toast(preference.getContext(), "Country is required!", Toast.LENGTH_LONG); + else { + CareLinkAuthenticator authenticator = new CareLinkAuthenticator(country, CareLinkCredentialStore.getInstance()); + if (authenticator.authenticate(getActivity())) { + JoH.static_toast(preference.getContext(), "Login completed!", Toast.LENGTH_LONG); + CareLinkFollowService.resetInstanceAndInvalidateSession(); + CollectionServiceStarter.restartCollectionServiceBackground(); + } + else + JoH.static_toast(preference.getContext(), "Login failed!", Toast.LENGTH_LONG); + } + } catch (InterruptedException e) { + + } + } + }.start(); + + return true; + } + }; + //Register preference handlers try { - carelinkFollowUser.setOnPreferenceChangeListener(carelinkFollowListener); - carelinkFollowPass.setOnPreferenceChangeListener(carelinkFollowListener); + //carelinkFollowUser.setOnPreferenceChangeListener(carelinkFollowListener); + //carelinkFollowPass.setOnPreferenceChangeListener(carelinkFollowListener); carelinkFollowCountry.setOnPreferenceChangeListener(carelinkFollowListener); carelinkFollowPatient.setOnPreferenceChangeListener(carelinkFollowListener); - carelinkFollowGracePeriod.setOnPreferenceChangeListener(carelinkFollowListener); - carelinkFollowMissedPollInterval.setOnPreferenceChangeListener(carelinkFollowListener); + carelinkFollowLogin.setOnPreferenceClickListener(carelinkLoginListener); + //carelinkFollowGracePeriod.setOnPreferenceChangeListener(carelinkFollowListener); + //carelinkFollowMissedPollInterval.setOnPreferenceChangeListener(carelinkFollowListener); } catch (Exception e) { // } //Remove CL prefs for NON CLFollower } else { try { - collectionCategory.removePreference(carelinkFollowUser); - collectionCategory.removePreference(carelinkFollowPass); + //collectionCategory.removePreference(carelinkFollowUser); + //collectionCategory.removePreference(carelinkFollowPass); collectionCategory.removePreference(carelinkFollowCountry); collectionCategory.removePreference(carelinkFollowPatient); + collectionCategory.removePreference(carelinkFollowLogin); collectionCategory.removePreference(carelinkFollowGracePeriod); collectionCategory.removePreference(carelinkFollowMissedPollInterval); collectionCategory.removePreference(carelinkFollowDownloadFingerBGs); @@ -1696,8 +1731,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { try { collectionCategory.removePreference(carelinkFollowCountry); collectionCategory.removePreference(carelinkFollowPatient); - collectionCategory.removePreference(carelinkFollowPass); - collectionCategory.removePreference(carelinkFollowUser); + //collectionCategory.removePreference(carelinkFollowPass); + //collectionCategory.removePreference(carelinkFollowUser); collectionCategory.removePreference(carelinkFollowGracePeriod); collectionCategory.removePreference(carelinkFollowMissedPollInterval); collectionCategory.removePreference(carelinkFollowDownloadFingerBGs); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1735b6499..2a1665d4e4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1336,16 +1336,22 @@ Nightscout Follow URL Also download treatments from Nightscout as follower Download Treatments + CareLink Country Select your CareLink country. CareLink Patient Username CareLink patient username (optional) + + Login + Login with browser Grace Period Grace period for data request in seconds Missed data poll interval diff --git a/app/src/main/res/xml/pref_data_source.xml b/app/src/main/res/xml/pref_data_source.xml index c67216fd6d..873bb23305 100644 --- a/app/src/main/res/xml/pref_data_source.xml +++ b/app/src/main/res/xml/pref_data_source.xml @@ -223,6 +223,7 @@ android:key="clfollow_country" android:summary="@string/summary_clfollow_country" android:title="@string/title_clfollow_country" /> + + +