From 643ddfae52e714d7d7d547f36876dc02d5c3b513 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 7 Jun 2019 11:07:08 +0200 Subject: [PATCH] cleanup, hook to preferences change --- .../tidepool/TidepoolJavaFragment.java | 2 +- .../general/tidepool/TidepoolPlugin.kt | 28 +++++--- .../general/tidepool/comm/TidepoolUploader.kt | 66 +++++++++++-------- .../general/tidepool/comm/UploadChunk.kt | 55 ++++++---------- .../tidepool/events/EventTidepoolStatus.kt | 16 ++++- .../receivers/NetworkChangeReceiver.java | 7 ++ .../androidaps/utils/FabricPrivacy.java | 2 +- .../androidaps/utils/LocaleHelper.java | 14 ++-- 8 files changed, 111 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolJavaFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolJavaFragment.java index e0294d2f7b..cbae2eb992 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolJavaFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolJavaFragment.java @@ -30,7 +30,7 @@ public class TidepoolJavaFragment extends SubscriberFragment { Button login = view.findViewById(R.id.tidepool_login); login.setOnClickListener(v1 -> { - TidepoolUploader.INSTANCE.doLogin(); + TidepoolUploader.INSTANCE.doLogin(false); }); Button uploadnow = view.findViewById(R.id.tidepool_uploadnow); uploadnow.setOnClickListener(v2 -> MainApp.bus().post(new EventTidepoolDoUpload())); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt index 224cf24616..53218f281b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt @@ -6,6 +6,8 @@ import info.nightscout.androidaps.Constants import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.events.EventNetworkChange +import info.nightscout.androidaps.events.EventNewBG +import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.interfaces.PluginDescription import info.nightscout.androidaps.interfaces.PluginType @@ -16,8 +18,8 @@ import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolR import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolUpdateGUI import info.nightscout.androidaps.plugins.general.tidepool.utils.RateLimit -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished import info.nightscout.androidaps.receivers.ChargingStateReceiver +import info.nightscout.androidaps.receivers.NetworkChangeReceiver import info.nightscout.androidaps.utils.SP import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.ToastUtils @@ -33,13 +35,11 @@ object TidepoolPlugin : PluginBase(PluginDescription() .description(R.string.description_tidepool) ) { private val log = LoggerFactory.getLogger(L.TIDEPOOL) - private var wifiConnected = false private val listLog = ArrayList() + @Suppress("DEPRECATION") // API level 24 to replace call var textLog = Html.fromHtml("") - var paused: Boolean = false - override fun onStart() { MainApp.bus().register(this) super.onStart() @@ -52,17 +52,19 @@ object TidepoolPlugin : PluginBase(PluginDescription() fun doUpload() { if (TidepoolUploader.connectionStatus == TidepoolUploader.ConnectionStatus.DISCONNECTED) - TidepoolUploader.doLogin() + TidepoolUploader.doLogin(true) else TidepoolUploader.doUpload() } @Suppress("UNUSED_PARAMETER") @Subscribe - fun onStatusEvent(ev: EventAutosensCalculationFinished) { + fun onStatusEvent(ev: EventNewBG) { + if (ev.bgReading!!.date!! < TidepoolUploader.getLastEnd()) + TidepoolUploader.setLastEnd(ev.bgReading!!.date!!) if (isEnabled(PluginType.GENERAL) && (!SP.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) - && (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || wifiConnected) + && (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || NetworkChangeReceiver.isWifiConnected()) && RateLimit.ratelimit("tidepool-new-data-upload", T.mins(4).secs().toInt())) doUpload() } @@ -73,6 +75,15 @@ object TidepoolPlugin : PluginBase(PluginDescription() doUpload() } + @Subscribe + fun onEventPreferenceChange(ev: EventPreferenceChange) { + if (ev.isChanged(R.string.key_tidepool_dev_servers) + || ev.isChanged(R.string.key_tidepool_username) + || ev.isChanged(R.string.key_tidepool_password) + ) + TidepoolUploader.resetInstance() + } + @Suppress("UNUSED_PARAMETER") @Subscribe fun onEventTidepoolResetData(ev: EventTidepoolResetData) { @@ -87,7 +98,7 @@ object TidepoolPlugin : PluginBase(PluginDescription() @Subscribe fun onEventNetworkChange(ev: EventNetworkChange) { - wifiConnected = ev.wifiConnected + // TODO start upload on wifi connect } @Subscribe @@ -116,6 +127,7 @@ object TidepoolPlugin : PluginBase(PluginDescription() newTextLog.append(log.toPreparedHtml()) } } + @Suppress("DEPRECATION") // API level 24 to replace call textLog = Html.fromHtml(newTextLog.toString()) } catch (e: OutOfMemoryError) { ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, "Out of memory!\nStop using this phone !!!", R.raw.error) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt index 44f1ccdbd5..d78088380c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt @@ -6,11 +6,12 @@ import info.nightscout.androidaps.BuildConfig import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.logging.L -import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus import info.nightscout.androidaps.plugins.general.tidepool.messages.* +import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.OKDialog import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.T import okhttp3.MediaType import okhttp3.OkHttpClient import okhttp3.RequestBody @@ -64,10 +65,11 @@ object TidepoolUploader { retrofit = null if (L.isEnabled(L.TIDEPOOL)) log.debug("Instance reset") + connectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED } @Synchronized - fun doLogin() { + fun doLogin(doUpload: Boolean = false) { if (connectionStatus == TidepoolUploader.ConnectionStatus.CONNECTED || connectionStatus == TidepoolUploader.ConnectionStatus.CONNECTING) { if (L.isEnabled(L.TIDEPOOL)) log.debug("Already connected") @@ -78,11 +80,11 @@ object TidepoolUploader { session = Session(AuthRequestMessage.getAuthRequestHeader(), SESSION_TOKEN_HEADER) if (session?.authHeader != null) { connectionStatus = TidepoolUploader.ConnectionStatus.CONNECTING - status("Connecting") + MainApp.bus().post(EventTidepoolStatus(("Connecting"))) val call = session!!.service?.getLogin(session?.authHeader!!) call?.enqueue(TidepoolCallback(session!!, "Login", { - startSession(session!!) + startSession(session!!, doUpload) }, { connectionStatus = TidepoolUploader.ConnectionStatus.FAILED; loginFailed() @@ -91,7 +93,7 @@ object TidepoolUploader { } else { if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot do login as user credentials have not been set correctly") connectionStatus = TidepoolUploader.ConnectionStatus.FAILED; - status("Invalid credentials") + MainApp.bus().post(EventTidepoolStatus(("Invalid credentials"))) releaseWakeLock() return } @@ -119,7 +121,7 @@ object TidepoolUploader { releaseWakeLock() } - fun startSession(session: Session) { + fun startSession(session: Session, doUpload: Boolean = false) { extendWakeLock(30000) if (session.authReply?.userid != null) { // See if we already have an open data set to write to @@ -128,14 +130,15 @@ object TidepoolUploader { datasetCall.enqueue(TidepoolCallback>(session, "Get Open Datasets", { if (session.datasetReply == null) { - status("Creating new dataset") + MainApp.bus().post(EventTidepoolStatus(("Creating new dataset"))) val call = session.service.openDataSet(session.token!!, session.authReply!!.userid!!, OpenDatasetRequestMessage().getBody()) call.enqueue(TidepoolCallback(session, "Open New Dataset", { connectionStatus = TidepoolUploader.ConnectionStatus.CONNECTED; - status("New dataset OK") + MainApp.bus().post(EventTidepoolStatus(("New dataset OK"))) + if (doUpload) doUpload() releaseWakeLock() }, { - status("New dataset FAILED") + MainApp.bus().post(EventTidepoolStatus(("New dataset FAILED"))) connectionStatus = TidepoolUploader.ConnectionStatus.FAILED; releaseWakeLock() })) @@ -145,18 +148,19 @@ object TidepoolUploader { // TODO: Wouldn't need to do this if we could block on the above `call.enqueue`. // ie, do the openDataSet conditionally, and then do `doUpload` either way. connectionStatus = TidepoolUploader.ConnectionStatus.CONNECTED; - status("Appending to existing dataset") + MainApp.bus().post(EventTidepoolStatus(("Appending to existing dataset"))) + if (doUpload) doUpload() releaseWakeLock() } }, { connectionStatus = TidepoolUploader.ConnectionStatus.FAILED; - status("Open dataset FAILED") + MainApp.bus().post(EventTidepoolStatus(("Open dataset FAILED"))) releaseWakeLock() })) } else { log.error("Got login response but cannot determine userid - cannot proceed") connectionStatus = TidepoolUploader.ConnectionStatus.FAILED; - status("Error userid") + MainApp.bus().post(EventTidepoolStatus(("Error userid"))) releaseWakeLock() } } @@ -175,43 +179,36 @@ object TidepoolUploader { releaseWakeLock() } else if (chunk.length == 2) { if (L.isEnabled(L.TIDEPOOL)) log.debug("Empty dataset - marking as succeeded") - status("No data to upload") + MainApp.bus().post(EventTidepoolStatus(("No data to upload"))) releaseWakeLock() } else { val body = RequestBody.create(MediaType.parse("application/json"), chunk) - status("Uploading") + MainApp.bus().post(EventTidepoolStatus(("Uploading"))) val call = session!!.service!!.doUpload(session!!.token!!, session!!.datasetReply!!.getUploadId()!!, body) call.enqueue(TidepoolCallback(session!!, "Data Upload", { - UploadChunk.setLastEnd(session!!.end) - status("Upload completed OK") + setLastEnd(session!!.end) + MainApp.bus().post(EventTidepoolStatus(("Upload completed OK"))) releaseWakeLock() }, { - status("Upload FAILED") + MainApp.bus().post(EventTidepoolStatus(("Upload FAILED"))) releaseWakeLock() })) } } - fun status(status: String) { - if (L.isEnabled(L.TIDEPOOL)) - log.debug("New status: $status") - MainApp.bus().post(EventTidepoolStatus(status)) - } - - fun deleteDataSet() { if (session?.datasetReply?.id != null) { extendWakeLock(60000) val call = session!!.service?.deleteDataSet(session!!.token!!, session!!.datasetReply!!.id!!) call?.enqueue(TidepoolCallback(session!!, "Delete Dataset", { connectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED - status("Dataset removed OK") + MainApp.bus().post(EventTidepoolStatus(("Dataset removed OK"))) releaseWakeLock() }, { connectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED - status("Dataset remove FAILED") + MainApp.bus().post(EventTidepoolStatus(("Dataset remove FAILED"))) releaseWakeLock() })) } else { @@ -219,6 +216,23 @@ object TidepoolUploader { } } + fun getLastEnd(): Long { + val result = SP.getLong(R.string.key_tidepool_last_end, 0) + return Math.max(result, DateUtil.now() - T.months(2).msecs()) + } + + fun setLastEnd(time: Long) { + if (time > getLastEnd()) { + SP.putLong(R.string.key_tidepool_last_end, time) + val friendlyEnd = DateUtil.dateAndTimeString(time) + MainApp.bus().post(EventTidepoolStatus(("Marking uploaded data up to $friendlyEnd"))) + if (L.isEnabled(L.TIDEPOOL)) log.debug("Updating last end to: " + DateUtil.dateAndTimeString(time)) + } else { + if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot set last end to: " + DateUtil.dateAndTimeString(time) + " vs " + DateUtil.dateAndTimeString(getLastEnd())) + } + } + + @Synchronized private fun extendWakeLock(ms: Long) { if (wl == null) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt index b26e0dce78..1ce2ea952d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt @@ -1,13 +1,12 @@ package info.nightscout.androidaps.plugins.general.tidepool.comm import info.nightscout.androidaps.MainApp -import info.nightscout.androidaps.R import info.nightscout.androidaps.logging.L import info.nightscout.androidaps.plugins.general.tidepool.elements.* +import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus import info.nightscout.androidaps.plugins.general.tidepool.utils.GsonInstance import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.SP import info.nightscout.androidaps.utils.T import org.slf4j.LoggerFactory import java.util.* @@ -22,13 +21,13 @@ object UploadChunk { if (session == null) return null - session.start = getLastEnd() + session.start = TidepoolUploader.getLastEnd() session.end = Math.min(session.start + MAX_UPLOAD_SIZE, DateUtil.now()) val result = get(session.start, session.end) if (result.length < 3) { if (L.isEnabled(L.TIDEPOOL)) log.debug("No records in this time period, setting start to best end time") - setLastEnd(Math.max(session.end, getOldestRecordTimeStamp())) + TidepoolUploader.setLastEnd(Math.max(session.end, getOldestRecordTimeStamp())) } return result } @@ -56,22 +55,6 @@ object UploadChunk { return GsonInstance.defaultGsonInstance().toJson(records) } - fun getLastEnd(): Long { - val result = SP.getLong(R.string.key_tidepool_last_end, 0) - return Math.max(result, DateUtil.now() - T.months(2).msecs()) - } - - fun setLastEnd(time: Long) { - if (time > getLastEnd()) { - SP.putLong(R.string.key_tidepool_last_end, time) - val friendlyEnd = DateUtil.dateAndTimeString(time) - TidepoolUploader.status("Marking uploaded data up to $friendlyEnd") - if (L.isEnabled(L.TIDEPOOL)) log.debug("Updating last end to: " + DateUtil.dateAndTimeString(time)) - } else { - if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot set last end to: " + DateUtil.dateAndTimeString(time) + " vs " + DateUtil.dateAndTimeString(getLastEnd())) - } - } - // numeric limits must match max time windows private fun getOldestRecordTimeStamp(): Long { @@ -99,39 +82,41 @@ object UploadChunk { return result } - internal fun getBloodTests(start: Long, end: Long): List { val readings = MainApp.getDbHelper().getCareportalEvents(start, end, true) - if (L.isEnabled(L.TIDEPOOL)) - log.debug("${readings.size} CPs selected for upload") - return BloodGlucoseElement.fromCareportalEvents(readings) + val selection = BloodGlucoseElement.fromCareportalEvents(readings) + if (selection.isNotEmpty()) + MainApp.bus().post(EventTidepoolStatus("${selection.size} BGs selected for upload")) + return selection } internal fun getBgReadings(start: Long, end: Long): List { val readings = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true) - if (L.isEnabled(L.TIDEPOOL)) - log.debug("${readings.size} BGs selected for upload") - return SensorGlucoseElement.fromBgReadings(readings) + val selection = SensorGlucoseElement.fromBgReadings(readings) + if (selection.isNotEmpty()) + MainApp.bus().post(EventTidepoolStatus("${selection.size} CGMs selected for upload")) + return selection } internal fun getBasals(start: Long, end: Long): List { val tbrs = MainApp.getDbHelper().getTemporaryBasalsDataFromTime(start, end, true) - if (L.isEnabled(L.TIDEPOOL)) - log.debug("${tbrs.size} TBRs selected for upload") - return BasalElement.fromTemporaryBasals(tbrs) + val selection = BasalElement.fromTemporaryBasals(tbrs) + if (selection.isNotEmpty()) + MainApp.bus().post(EventTidepoolStatus("${selection.size} TBRs selected for upload")) + return selection } internal fun getProfiles(start: Long, end: Long): List { val pss = MainApp.getDbHelper().getProfileSwitchEventsFromTime(start, end, true) - if (L.isEnabled(L.TIDEPOOL)) - log.debug("${pss.size} ProfileSwitches selected for upload") - val results = LinkedList() + val selection = LinkedList() for (ps in pss) { val pe = ProfileElement(ps) - results.add(pe) + selection.add(pe) } - return results + if (selection.size > 0) + MainApp.bus().post(EventTidepoolStatus("${selection.size} ProfileSwitches selected for upload")) + return selection } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt index d32c073af7..949cc33e13 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt @@ -1,13 +1,23 @@ package info.nightscout.androidaps.plugins.general.tidepool.events import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.logging.L import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.LocaleHelper +import org.slf4j.LoggerFactory import java.text.SimpleDateFormat -class EventTidepoolStatus (val status: String) : Event() { - var date : Long = DateUtil.now() +class EventTidepoolStatus(val status: String) : Event() { + private val log = LoggerFactory.getLogger(L.TIDEPOOL) - internal var timeFormat = SimpleDateFormat("HH:mm:ss") + var date: Long = DateUtil.now() + + init { + if (L.isEnabled(L.TIDEPOOL)) + log.debug("New status: $status") + } + + internal var timeFormat = SimpleDateFormat("HH:mm:ss", LocaleHelper.getLocale()) fun toPreparedHtml(): StringBuilder { val stringBuilder = StringBuilder() diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java index 9a3108e98c..a2ab418914 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java @@ -21,6 +21,8 @@ public class NetworkChangeReceiver extends BroadcastReceiver { private static Logger log = LoggerFactory.getLogger(L.CORE); + private static EventNetworkChange lastEvent = null; + @Override public void onReceive(final Context context, final Intent intent) { EventNetworkChange event = grabNetworkStatus(context); @@ -61,6 +63,11 @@ public class NetworkChangeReceiver extends BroadcastReceiver { log.debug("NETCHANGE: Disconnected."); } + lastEvent = event; return event; } + + public static boolean isWifiConnected() { + return lastEvent != null && lastEvent.wifiConnected; + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.java b/app/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.java index 54f06bfaf0..3dd7fe4fd9 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.java @@ -121,7 +121,7 @@ public class FabricPrivacy { .replace(".net/", ":"); MainApp.getFirebaseAnalytics().setUserProperty("Mode", BuildConfig.APPLICATION_ID + "-" + closedLoopEnabled); - MainApp.getFirebaseAnalytics().setUserProperty("Language", LocaleHelper.getLanguage(MainApp.instance())); + MainApp.getFirebaseAnalytics().setUserProperty("Language", LocaleHelper.getLanguage()); MainApp.getFirebaseAnalytics().setUserProperty("Version", BuildConfig.VERSION); MainApp.getFirebaseAnalytics().setUserProperty("HEAD", BuildConfig.HEAD); MainApp.getFirebaseAnalytics().setUserProperty("Remote", remote); diff --git a/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.java b/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.java index bf0b2796ee..d2e4c0622b 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.java @@ -21,17 +21,17 @@ public class LocaleHelper { private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language"; public static void onCreate(Context context) { - String lang = getPersistedData(context, Locale.getDefault().getLanguage()); + String lang = getPersistedData(Locale.getDefault().getLanguage()); setLocale(context, lang); } public static void onCreate(Context context, String defaultLanguage) { - String lang = getPersistedData(context, defaultLanguage); + String lang = getPersistedData(defaultLanguage); setLocale(context, lang); } - public static String getLanguage(Context context) { - return getPersistedData(context, Locale.getDefault().getLanguage()); + public static String getLanguage() { + return getPersistedData(Locale.getDefault().getLanguage()); } public static void setLocale(Context context, String language) { @@ -39,7 +39,7 @@ public class LocaleHelper { updateResources(context, language); } - private static String getPersistedData(Context context, String defaultLanguage) { + private static String getPersistedData(String defaultLanguage) { return SP.getString(SELECTED_LANGUAGE, defaultLanguage); } @@ -62,4 +62,8 @@ public class LocaleHelper { resources.updateConfiguration(configuration, resources.getDisplayMetrics()); } + + public static Locale getLocale() { + return new Locale(getPersistedData("en")); + } } \ No newline at end of file