From 65bd50117fc824828cbf48809d424818e7b0cd52 Mon Sep 17 00:00:00 2001 From: Pogman Date: Mon, 24 Jul 2017 19:42:21 +0100 Subject: [PATCH 1/8] status log persisted and paged --- .../android/UploaderApplication.java | 23 +- .../android/medtronic/MainActivity.java | 248 ++++++++++++++---- .../nightscout/android/utils/StatusStore.java | 45 ++++ app/src/main/res/layout/activity_main.xml | 46 +++- 4 files changed, 311 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/info/nightscout/android/utils/StatusStore.java diff --git a/app/src/main/java/info/nightscout/android/UploaderApplication.java b/app/src/main/java/info/nightscout/android/UploaderApplication.java index 1bfc87f..9fe2827 100644 --- a/app/src/main/java/info/nightscout/android/UploaderApplication.java +++ b/app/src/main/java/info/nightscout/android/UploaderApplication.java @@ -8,9 +8,15 @@ import com.crashlytics.android.Crashlytics; import com.crashlytics.android.answers.Answers; +import info.nightscout.android.model.medtronicNg.BasalRate; +import info.nightscout.android.model.medtronicNg.BasalSchedule; +import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo; +import info.nightscout.android.model.medtronicNg.PumpInfo; +import info.nightscout.android.model.medtronicNg.PumpStatusEvent; import io.fabric.sdk.android.Fabric; import io.realm.Realm; import io.realm.RealmConfiguration; +import io.realm.annotations.RealmModule; import uk.co.chrisjenx.calligraphy.CalligraphyConfig; /** @@ -43,9 +49,24 @@ public void onCreate() { Realm.init(this); RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() + .modules(new MainModule()) .deleteRealmIfMigrationNeeded() .build(); - +/* + RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder() + .name("storerealm.realm") + .modules(new StoreModule()) + .deleteRealmIfMigrationNeeded() + .build(); +*/ Realm.setDefaultConfiguration(realmConfiguration); } + + @RealmModule(classes = {BasalRate.class, BasalSchedule.class, ContourNextLinkInfo.class, PumpInfo.class, PumpStatusEvent.class}) + public class MainModule { + } +// @RealmModule(classes = {StatusStore.class}) +// public class StoreModule { +// } + } diff --git a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java index f0b9dd1..745c28f 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java @@ -64,8 +64,6 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -import java.util.Queue; -import java.util.concurrent.ArrayBlockingQueue; import info.nightscout.android.R; import info.nightscout.android.USB.UsbHidDriver; @@ -78,10 +76,13 @@ import info.nightscout.android.settings.SettingsActivity; import info.nightscout.android.utils.ConfigurationStore; import info.nightscout.android.utils.DataStore; +import info.nightscout.android.utils.StatusStore; import io.realm.Realm; import io.realm.RealmChangeListener; +import io.realm.RealmConfiguration; import io.realm.RealmResults; import io.realm.Sort; +import io.realm.annotations.RealmModule; import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper; public class MainActivity extends AppCompatActivity implements OnSharedPreferenceChangeListener, OnEulaAgreedTo { @@ -99,11 +100,17 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private SharedPreferences prefs = null; private PumpInfo mActivePump; private TextView mTextViewLog; // This will eventually move to a status page. + private TextView mTextViewLogButtonTop; + private TextView mTextViewLogButtonTopRecent; + private TextView mTextViewLogButtonBottom; + private TextView mTextViewLogButtonBottomRecent; + private ScrollView mScrollView; private GraphView mChart; private Handler mUiRefreshHandler = new Handler(); private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable(); private Realm mRealm; + private Realm storeRealm; private StatusMessageReceiver statusMessageReceiver = new StatusMessageReceiver(); private UsbReceiver usbReceiver = new UsbReceiver(); private BatteryReceiver batteryReceiver = new BatteryReceiver(); @@ -117,6 +124,10 @@ protected void sendStatus(String message) { LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent); } + @RealmModule(classes = {StatusStore.class}) + public class StoreModule { + } + /** * calculate the next poll timestamp based on last svg event * @@ -148,6 +159,7 @@ public static long getNextPoll(PumpStatusEvent pumpStatusData) { return nextPoll; } + @Override public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "onCreate called"); @@ -155,6 +167,13 @@ public void onCreate(Bundle savedInstanceState) { mRealm = Realm.getDefaultInstance(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() + .name("storerealm.realm") + .modules(new StoreModule()) + .deleteRealmIfMigrationNeeded() + .build(); + storeRealm = Realm.getInstance(realmConfiguration); + RealmResults data = mRealm.where(PumpStatusEvent.class) .findAllSorted("eventDate", Sort.DESCENDING); if (data.size() > 0) @@ -315,6 +334,39 @@ public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { mTextViewLog = (TextView) findViewById(R.id.textview_log); mScrollView = (ScrollView) findViewById(R.id.scrollView); mScrollView.setSmoothScrollingEnabled(true); + mTextViewLogButtonTop = (TextView) findViewById(R.id.button_log_top); + mTextViewLogButtonTop.setVisibility(View.GONE); + mTextViewLogButtonTop.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + statusMessageReceiver.changeStatusViewOlder(); + } + }); + mTextViewLogButtonTopRecent = (TextView) findViewById(R.id.button_log_top_recent); + mTextViewLogButtonTopRecent.setVisibility(View.GONE); + mTextViewLogButtonTopRecent.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + statusMessageReceiver.changeStatusViewRecent(); + } + }); + + mTextViewLogButtonBottom = (TextView) findViewById(R.id.button_log_bottom); + mTextViewLogButtonBottom.setVisibility(View.GONE); + mTextViewLogButtonBottom.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + statusMessageReceiver.changeStatusViewNewer(); + } + }); + mTextViewLogButtonBottomRecent = (TextView) findViewById(R.id.button_log_bottom_recent); + mTextViewLogButtonBottomRecent.setVisibility(View.GONE); + mTextViewLogButtonBottomRecent.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + statusMessageReceiver.changeStatusViewRecent(); + } + }); mChart = (GraphView) findViewById(R.id.chart); @@ -559,16 +611,15 @@ protected void onResume() { Log.i(TAG, "onResume called"); super.onResume(); // Focus status log to most recent on returning to app - mScrollView.post(new Runnable() { - public void run() { - mScrollView.fullScroll(View.FOCUS_DOWN); - } - }); + statusMessageReceiver.changeStatusViewRecent(); } @Override protected void onDestroy() { Log.i(TAG, "onDestroy called"); + statusMessageReceiver.addMessage(MedtronicCnlIntentService.ICON_INFO + "Shutting down uploader."); + statusMessageReceiver.addMessage("-----------------------------------------------------"); + super.onDestroy(); unregisterReceiver(usbReceiver); @@ -577,6 +628,10 @@ protected void onDestroy() { PreferenceManager.getDefaultSharedPreferences(getBaseContext()).unregisterOnSharedPreferenceChangeListener(this); cancelDisplayRefreshLoop(); + if (!storeRealm.isClosed()) { + storeRealm.close(); + } + if (!mRealm.isClosed()) { mRealm.close(); } @@ -642,7 +697,7 @@ private PumpInfo getActivePump() { // remove listener on old pump mRealm.executeTransaction(new Realm.Transaction() { @Override - public void execute(Realm sRealm) { + public void execute(Realm realm) { mActivePump.removeAllChangeListeners(); } }); @@ -746,64 +801,161 @@ public static String renderTrendSymbol(PumpStatusEvent.CGM_TREND trend) { } private class StatusMessageReceiver extends BroadcastReceiver { - private class StatusMessage { - private long timestamp; - private String message; + private static final int STATUS_LINES = 300; + private static final int STATUS_STALE = 72 * 60 * 60 * 1000; + private int statusView = 0; + private int statusViewLastSession = 0; - StatusMessage(String message) { - this(System.currentTimeMillis(), message); - } + @Override + public void onReceive(Context context, Intent intent) { + final String message = intent.getStringExtra(MedtronicCnlIntentService.Constants.EXTENDED_DATA); + Log.i(TAG, "Message Receiver: " + message); + addMessage(message); + } + + private void addMessage(final String message) { + if (storeRealm.isClosed()) return; - StatusMessage(long timestamp, String message) { - this.timestamp = timestamp; - this.message = message; + storeRealm.executeTransaction( + new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(StatusStore.class).StatusMessage(message); + } + }); + statusViewLastSession++; // last session view pointer + if (statusView > 0) statusView++; // move the view pointer when not on first page + showLog(); + + if (statusView == 0) { + // auto scroll status log + if ((mScrollView.getChildAt(0).getBottom() < mScrollView.getHeight()) || ((mScrollView.getChildAt(0).getBottom() - mScrollView.getScrollY() - mScrollView.getHeight()) < (mScrollView.getHeight() / 3))) { + mScrollView.post(new Runnable() { + public void run() { + mScrollView.fullScroll(View.FOCUS_DOWN); + } + }); + } } - public String toString() { - return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(timestamp) + ": " + message; + final RealmResults results = storeRealm.where(StatusStore.class) + .lessThan("timestamp", System.currentTimeMillis() - STATUS_STALE) + .findAll(); + if (results.size() > 0) { + storeRealm.executeTransaction( + new Realm.Transaction() { + @Override + public void execute(Realm realm) { + results.deleteAllFromRealm(); + } + }); } } - private final Queue messages = new ArrayBlockingQueue<>(400); + private void showLog() { + RealmResults results = storeRealm.where(StatusStore.class) + .findAllSorted("timestamp", Sort.DESCENDING); - @Override - public void onReceive(Context context, Intent intent) { - String message = intent.getStringExtra(MedtronicCnlIntentService.Constants.EXTENDED_DATA); - Log.i(TAG, "Message Receiver: " + message); + int remain = results.size() - statusView; + int segment = remain; + if (statusView == 0 && statusViewLastSession < STATUS_LINES) segment = statusViewLastSession; + if (segment > STATUS_LINES) segment = STATUS_LINES; - synchronized (messages) { - while (messages.size() > 398) { - messages.poll(); - } - messages.add(new StatusMessage(message)); + if (segment > 0) { + StringBuilder sb = new StringBuilder(); + for (int index = statusView; index < statusView + segment; index++) + sb.insert(0, results.get(index) + (sb.length() > 0 ? "\n" : "")); + mTextViewLog.setText(sb.toString(), BufferType.EDITABLE); } - StringBuilder sb = new StringBuilder(); - for (StatusMessage msg : messages) { - if (sb.length() > 0) - sb.append("\n"); - sb.append(msg); + if (statusView > 0) { + mTextViewLogButtonBottom.setVisibility(View.VISIBLE); + mTextViewLogButtonBottomRecent.setVisibility(View.VISIBLE); + } else { + mTextViewLogButtonBottom.setVisibility(View.GONE); + mTextViewLogButtonBottomRecent.setVisibility(View.GONE); } - - mTextViewLog.setText(sb.toString(), BufferType.EDITABLE); - - // auto scroll status log - if ((mScrollView.getChildAt(0).getBottom() < mScrollView.getHeight()) || ((mScrollView.getChildAt(0).getBottom() - mScrollView.getScrollY() - mScrollView.getHeight()) < (mScrollView.getHeight() / 3))) { - mScrollView.post(new Runnable() { - public void run() { - mScrollView.fullScroll(View.FOCUS_DOWN); - } - }); + if (remain > segment) { + mTextViewLogButtonTop.setVisibility(View.VISIBLE); + } else { + mTextViewLogButtonTop.setVisibility(View.GONE); + } + if (statusView > 0 || mScrollView.getChildAt(0).getBottom() > mScrollView.getHeight() + 100) { + mTextViewLogButtonTopRecent.setVisibility(View.VISIBLE); + } else { + mTextViewLogButtonTopRecent.setVisibility(View.GONE); } } - public void clearMessages() { - synchronized (messages) { - messages.clear(); + private void clearMessages() { + final RealmResults results = storeRealm.where(StatusStore.class) + .findAll(); + if (results.size() > 0) { + storeRealm.executeTransaction( + new Realm.Transaction() { + @Override + public void execute(Realm realm) { + results.deleteAllFromRealm(); + } + }); } - mTextViewLog.setText("", BufferType.EDITABLE); + mTextViewLogButtonTop.setVisibility(View.GONE); + mTextViewLogButtonTopRecent.setVisibility(View.GONE); + mTextViewLogButtonBottom.setVisibility(View.GONE); + mTextViewLogButtonBottomRecent.setVisibility(View.GONE); + statusView = 0; + statusViewLastSession = 0; } + + private void changeStatusViewOlder() { + if (statusView == 0 && statusViewLastSession < STATUS_LINES) { + statusView = statusViewLastSession; + } else { + statusView += STATUS_LINES; + } + showLog(); + mScrollView.post(new Runnable() { + public void run() { + mScrollView.fullScroll(View.FOCUS_DOWN); + if (statusView > 0 || mScrollView.getChildAt(0).getBottom() > mScrollView.getHeight() + 100) { + mTextViewLogButtonTopRecent.setVisibility(View.VISIBLE); + } else { + mTextViewLogButtonTopRecent.setVisibility(View.GONE); + } + } + }); + } + private void changeStatusViewNewer() { + statusView -= STATUS_LINES; + if (statusView < 0) statusView = 0; + showLog(); + mScrollView.post(new Runnable() { + public void run() { + mScrollView.fullScroll(View.FOCUS_UP); + if (statusView > 0 || mScrollView.getChildAt(0).getBottom() > mScrollView.getHeight() + 100) { + mTextViewLogButtonTopRecent.setVisibility(View.VISIBLE); + } else { + mTextViewLogButtonTopRecent.setVisibility(View.GONE); + } + } + }); + } + private void changeStatusViewRecent() { + statusView = 0; + showLog(); + mScrollView.post(new Runnable() { + public void run() { + mScrollView.fullScroll(View.FOCUS_DOWN); + if (statusView > 0 || mScrollView.getChildAt(0).getBottom() > mScrollView.getHeight() + 100) { + mTextViewLogButtonTopRecent.setVisibility(View.VISIBLE); + } else { + mTextViewLogButtonTopRecent.setVisibility(View.GONE); + } + } + }); + } + } private class RefreshDisplayRunnable implements Runnable { diff --git a/app/src/main/java/info/nightscout/android/utils/StatusStore.java b/app/src/main/java/info/nightscout/android/utils/StatusStore.java new file mode 100644 index 0000000..0a1946b --- /dev/null +++ b/app/src/main/java/info/nightscout/android/utils/StatusStore.java @@ -0,0 +1,45 @@ +package info.nightscout.android.utils; + +import android.text.format.DateUtils; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import io.realm.RealmObject; +import io.realm.annotations.Index; + +/** + * Created by Pogman on 22.7.17. + */ + +public class StatusStore extends RealmObject { + @Index + private long timestamp; + private String message; + + public void StatusMessage(String message) { + StatusMessage(System.currentTimeMillis(), message); + } + + public void StatusMessage(long timestamp, String message) { + this.timestamp = timestamp; + this.message = message; + } + + public String toString() { + DateFormat df = new SimpleDateFormat("E HH:mm:ss"); + return df.format(timestamp) + ": " + message; + +/* + if (DateUtils.isToday(timestamp)) { + DateFormat df = new SimpleDateFormat("h:mm:ss a"); + return df.format(timestamp) + ": " + message; + } else { + DateFormat df = new SimpleDateFormat("E h:mm:ss a"); + return df.format(timestamp) + ": " + message; + } +*/ + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8d2fad1..3d4f09e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -135,14 +135,56 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> + + +