diff --git a/app/build.gradle b/app/build.gradle index 99f75893c2..c28da624bb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -369,9 +369,12 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' } - androidTestImplementation 'androidx.test.ext:junit:1.1.1', { + androidTestImplementation 'androidx.test.ext:junit:1.1.5', { exclude group: 'com.android.support', module: 'support-annotations' } + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0' androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.1.0' // add this for intent mocking support //androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.1' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ebbec48de7..f447b1aa5c 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -64,6 +64,21 @@ -keep class android.support.v7.widget.SearchView { *; } -keep class kotlinx.serialization.Serializable { *; } +# As long as we only deserialize (from a JSON string into an `NSDeviceStatus` +# object in the class `AAPSStatusHandler`) we can simply ignore warnings related +# to kotlinx serialization. +# These rule should not cause problems: if a project actually relies on +# serialization, then much more than just this class will be required, +# so telling Proguard not to worry if this is missing will not prevent it +# from emitting errors for code that does use serialization but somehow forgot +# to depend on it. +-dontwarn kotlinx.serialization.Serializable + +# The lib net.sf.kxml:kxml2:2.3.0 is referenced in same required libraries used for +# Android testing. R8 is showing missing classes warnings which can be safely ignored. +-dontwarn org.kxml2.io.KXmlParser +-dontwarn org.kxml2.io.KXmlSerializer + -dontwarn java.util.concurrent.** -keep class rx.schedulers.Schedulers { diff --git a/app/src/androidTest/java/com/eveningoutpost/dexdrip/HomeEspressoTest.java b/app/src/androidTest/java/com/eveningoutpost/dexdrip/HomeEspressoTest.java index 0456ea66e0..3159bfe434 100644 --- a/app/src/androidTest/java/com/eveningoutpost/dexdrip/HomeEspressoTest.java +++ b/app/src/androidTest/java/com/eveningoutpost/dexdrip/HomeEspressoTest.java @@ -5,15 +5,28 @@ */ +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static com.schibsted.spain.barista.BaristaClickActions.click; +import static com.schibsted.spain.barista.BaristaScrollActions.scrollTo; +import static com.schibsted.spain.barista.custom.NestedEnabledScrollToAction.scrollTo; +import static org.hamcrest.core.AllOf.allOf; + import android.app.Activity; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.annotation.UiThreadTest; -import android.support.test.espresso.ViewInteraction; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; import android.view.WindowManager; +import androidx.test.InstrumentationRegistry; +import androidx.test.annotation.UiThreadTest; +import androidx.test.espresso.ViewInteraction; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + import com.schibsted.spain.barista.flakyespresso.AllowFlaky; import org.junit.After; @@ -26,18 +39,6 @@ import java.io.File; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.RootMatchers.isDialog; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static com.schibsted.spain.barista.BaristaClickActions.click; -import static com.schibsted.spain.barista.BaristaScrollActions.scrollTo; -import static com.schibsted.spain.barista.custom.NestedEnabledScrollToAction.scrollTo; -import static org.hamcrest.core.AllOf.allOf; - @RunWith(AndroidJUnit4.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class HomeEspressoTest { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 73df543f1f..35e672da9d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -703,6 +703,12 @@ + + 200000) { + if (builder.length() > MAX_LOG_PACKAGE_SIZE) { JoH.static_toast_long(this, "Could not package up all logs, using most recent"); + builder.append("\n\nOnly the most recent logs have been included to limit the file size.\n"); break; } } - startActivity(new Intent(getApplicationContext(), SendFeedBack.class).putExtra("generic_text", builder.toString())); + + builder.insert(0, JoH.getDeviceDetails() + "\n" + JoH.getVersionDetails() + "\n" + getBestCollectorHardwareName() + "\n===\n" + "\nLog data:\n"); // Adds device, version and collector details before the log. + builder.append("\n\nCaptured: " + JoH.dateTimeText(JoH.tsl())); // Adds date and time of capture after the log. + + return builder.toString(); } // View model container - accessible binding methods must be declared public @@ -636,5 +651,3 @@ public void onBindBinding(ViewDataBinding binding, int bindingVariable, @LayoutR } } - - diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/GcmActivity.java b/app/src/main/java/com/eveningoutpost/dexdrip/GcmActivity.java index 447dfef44f..be2e219f6e 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/GcmActivity.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/GcmActivity.java @@ -16,6 +16,7 @@ import android.widget.Toast; +import com.eveningoutpost.dexdrip.cloud.jamcm.JamCm; import com.eveningoutpost.dexdrip.models.BgReading; import com.eveningoutpost.dexdrip.models.BloodTest; import com.eveningoutpost.dexdrip.models.Calibration; @@ -216,7 +217,8 @@ private static void queueCheckOld(Context context, boolean recursive) { try { Log.i(TAG, "Resending unacknowledged queue item: " + datum.bundle.getString("action") + datum.bundle.getString("payload")); datum.resent++; - GoogleCloudMessaging.getInstance(context).send(senderid + "@gcm.googleapis.com", Integer.toString(msgId.incrementAndGet()), datum.bundle); + // GoogleCloudMessaging.getInstance(context).send(senderid + "@gcm.googleapis.com", Integer.toString(msgId.incrementAndGet()), datum.bundle); + JamCm.sendMessage(datum.bundle); } catch (Exception e) { Log.e(TAG, "Got exception during resend: " + e.toString()); } @@ -650,7 +652,7 @@ public static void push_external_status_update(long timestamp, String statusLine } } - static String myIdentity() { + public static String myIdentity() { // TODO prefs override possible return GoogleDriveInterface.getDriveIdentityString(); } @@ -762,19 +764,20 @@ private static synchronized String sendMessageNow(String identity, String action Log.e(TAG, "Queue size exceeded"); Home.toaststaticnext("Maximum Sync Queue size Exceeded!"); } - final GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(xdrip.getAppContext()); + // final GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(xdrip.getAppContext()); if (token == null) { Log.e(TAG, "GCM token is null - cannot sendMessage"); return ""; } String messageid = Integer.toString(msgId.incrementAndGet()); - gcm.send(senderid + "@gcm.googleapis.com", messageid, data); + // gcm.send(senderid + "@gcm.googleapis.com", messageid, data); if (last_ack == -1) last_ack = JoH.tsl(); last_send_previous = last_send; last_send = JoH.tsl(); + JamCm.sendMessage(data); msg = "Sent message OK " + messageid; DesertSync.fromGCM(data); - } catch (IOException ex) { + } catch (Exception ex) { msg = "Error :" + ex.getMessage(); } Log.d(TAG, "Return msg in SendMessage: " + msg); diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/GcmListenerSvc.java b/app/src/main/java/com/eveningoutpost/dexdrip/GcmListenerSvc.java index 0e2211199d..8e526d4cf6 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/GcmListenerSvc.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/GcmListenerSvc.java @@ -16,6 +16,7 @@ import android.os.PowerManager; import android.util.Base64; +import com.eveningoutpost.dexdrip.cloud.jamcm.JamCm; import com.eveningoutpost.dexdrip.models.BgReading; import com.eveningoutpost.dexdrip.models.BloodTest; import com.eveningoutpost.dexdrip.models.Calibration; @@ -167,8 +168,7 @@ public void onMessageReceived(RemoteMessage rmessage) { String xfrom = data.getString("xfrom"); String payload = data.getString("datum", data.getString("payload")); String action = data.getString("action"); - - if ((xfrom != null) && (xfrom.equals(GcmActivity.token))) { + if ((xfrom != null) && (xfrom.equals(GcmActivity.token) || xfrom.equals(JamCm.getId()))) { GcmActivity.queueAction(action + payload); return; } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Home.java b/app/src/main/java/com/eveningoutpost/dexdrip/Home.java index 75ec69b26e..d94d922ad7 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/Home.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Home.java @@ -70,6 +70,7 @@ import android.widget.TextView; import android.widget.Toast; +import com.eveningoutpost.dexdrip.g5model.DexSyncKeeper; import com.eveningoutpost.dexdrip.g5model.DexTimeKeeper; import com.eveningoutpost.dexdrip.g5model.Ob1G5StateMachine; import com.eveningoutpost.dexdrip.g5model.SensorDays; @@ -645,7 +646,7 @@ private boolean checkBatteryOptimization() { Log.d(TAG, "Requesting ignore battery optimization"); if (((dialog == null) || (!dialog.isShowing())) - && (PersistentStore.incrementLong("asked_battery_optimization") < 40)) { + && (PersistentStore.incrementLong("asked_battery_optimization") < 40000)) { JoH.show_ok_dialog(this, gs(R.string.please_allow_permission), gs(R.string.xdrip_needs_whitelisting_for_proper_performance), new Runnable() { @RequiresApi(api = Build.VERSION_CODES.M) @@ -1912,7 +1913,7 @@ public void onReceive(Context ctx, Intent intent) { NFControl.initNFC(this, false); if (get_follower() || get_master()) { - GcmActivity.checkSync(this); + // GcmActivity.checkSync(this); } checkWifiSleepPolicy(); @@ -2630,11 +2631,11 @@ private void updateCurrentBgInfoCommon(DexCollectionType collector, TextView not } if (!isSensorActive) { - // Define a variable (notConnectedToG6Yet) that is only true if Native G6 is chosen, but, transmitter days is unknown. - boolean notConnectedToG6Yet = DexCollectionType.getDexCollectionType() == DexcomG5 && Pref.getBooleanDefaultFalse("ob1_g5_use_transmitter_alg") && Pref.getBooleanDefaultFalse("using_g6") && DexTimeKeeper.getTransmitterAgeInDays(getTransmitterID()) == -1; - if (notConnectedToG6Yet || shortTxId()) { // Only if G6 has been selected and transmitter days is unknown, or if G7 has been selected. + // Define a variable (notConnectedToG6Yet) that is only true if Native G6 is chosen, but, transmitter days is unknown or not synced yet. + boolean notConnectedToG6Yet = DexCollectionType.getDexCollectionType() == DexcomG5 && Pref.getBooleanDefaultFalse("ob1_g5_use_transmitter_alg") && Pref.getBooleanDefaultFalse("using_g6") && (DexTimeKeeper.getTransmitterAgeInDays(getTransmitterID()) == -1 || !DexSyncKeeper.isReady(getTransmitterID())); + if (notConnectedToG6Yet || shortTxId()) { // Only if G6 has been selected and transmitter is not synced yet, or if G7 has been selected. notificationText.setText(R.string.wait_to_connect); - } else { // Only if G6 is not selected or G6 transmitter days is known. + } else { // Only if G6 is not selected or G6 transmitter is synced. notificationText.setText(R.string.now_start_your_sensor); } @@ -2653,7 +2654,7 @@ private void updateCurrentBgInfoCommon(DexCollectionType collector, TextView not dialog.show(); } else { if (!Experience.gotData() && !QuickSettingsDialogs.isDialogShowing() && !notConnectedToG6Yet && JoH.ratelimit("start-sensor_prompt", 20)) { - // Show the dialog only if there is no data, and there is no dialog, and G6 is not selected or G6 is connected, and the rate limit is satisfied. + // Show the start sensor prompt only if G6 is not selected or the G6 transmitter is synchronized. final AlertDialog.Builder builder = new AlertDialog.Builder(this); final Context context = this; builder.setTitle(getString(R.string.start_sensor) + "?"); diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/MegaStatus.java b/app/src/main/java/com/eveningoutpost/dexdrip/MegaStatus.java index de93a3a4cc..a985fe6ac4 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/MegaStatus.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/MegaStatus.java @@ -119,7 +119,7 @@ private void addAsection(String section, String title) { } private static final String G4_STATUS = "BT Device"; - public static final String G5_STATUS = "G5/G6/G7 Status"; + public static final String G5_STATUS = "Dex Status"; private static final String MEDTRUM_STATUS = "Medtrum Status"; private static final String IP_COLLECTOR = "IP Collector"; private static final String XDRIP_PLUS_SYNC = "Followers"; @@ -168,7 +168,7 @@ private void populateSectionList() { } if (dexCollectionType.equals(DexcomG5)) { if (Pref.getBooleanDefaultFalse(Ob1G5CollectionService.OB1G5_PREFS)) { - addAsection(G5_STATUS, "OB1 G5/G6/G7 Collector and Transmitter Status"); + addAsection(G5_STATUS, "G6/Dex1/G7/1+ Collector/Transmitter Status"); } else { addAsection(G5_STATUS, "G5 Collector and Transmitter Status"); } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/NavDrawerBuilder.java b/app/src/main/java/com/eveningoutpost/dexdrip/NavDrawerBuilder.java index f8fcca76d7..daf241c321 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/NavDrawerBuilder.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/NavDrawerBuilder.java @@ -18,7 +18,6 @@ import com.eveningoutpost.dexdrip.utilitymodels.CollectionServiceStarter; import com.eveningoutpost.dexdrip.utilitymodels.Experience; import com.eveningoutpost.dexdrip.stats.StatsActivity; -import com.eveningoutpost.dexdrip.utilitymodels.Pref; import com.eveningoutpost.dexdrip.utils.DexCollectionType; import com.eveningoutpost.dexdrip.utils.Preferences; @@ -103,7 +102,7 @@ public NavDrawerBuilder(final Context context) { } } } - if (!getBestCollectorHardwareName().equals("G7") || Pref.getBooleanDefaultFalse("engineering_mode")) { // If we are using G7, offer the stop sensor option in engineering mode only + if (!getBestCollectorHardwareName().equals("G7")) { // If we are using G7, there will be no stop sensor option in the menu. this.nav_drawer_options.add(context.getString(R.string.stop_sensor)); this.nav_drawer_intents.add(new Intent(context, StopSensor.class)); } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/alert/SensorExpiry.java b/app/src/main/java/com/eveningoutpost/dexdrip/alert/SensorExpiry.java index dcd1f455d0..f7271a52c7 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/alert/SensorExpiry.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/alert/SensorExpiry.java @@ -41,7 +41,7 @@ public SensorExpiry() { @Override public boolean activate() { - val expiry = niceTimeScalarNatural(SensorDays.get().getRemainingSensorPeriodInMs()); + val expiry = niceTimeScalarNatural(SensorDays.get().getRemainingSensorPeriodInMs(), 1); val notificationId = SENSORY_EXPIRY_NOTIFICATION_ID; cancelNotification(notificationId); val expireMsg = String.format("Sensor will expire in %s", expiry); // TODO i18n and format string diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cloud/jamcm/JamCm.java b/app/src/main/java/com/eveningoutpost/dexdrip/cloud/jamcm/JamCm.java new file mode 100644 index 0000000000..0162ff2d77 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cloud/jamcm/JamCm.java @@ -0,0 +1,103 @@ +package com.eveningoutpost.dexdrip.cloud.jamcm; + +import static com.eveningoutpost.dexdrip.utils.CipherUtils.hexToBytes; + +import android.os.Bundle; + +import com.eveningoutpost.dexdrip.GcmActivity; +import com.eveningoutpost.dexdrip.models.JoH; +import com.eveningoutpost.dexdrip.models.UserError; +import com.eveningoutpost.dexdrip.utils.CipherUtils; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +import lombok.val; + +/** + * JamOrHam + *

+ * Replacement for GCM + */ +public class JamCm { + + private static final String TAG = "JamCm"; + private static final String serverInstance = "jamcm3749021"; + private static final String serverDomain = "bluejay.website"; + private static final String serverAddress = serverInstance + "." + serverDomain; + private static final int serverPort = 5228; + + private static final byte PROTOCOL_VERSION = 1; + + /** + * @noinspection DataFlowIssue + */ + public static String getId() { + if (GcmActivity.token == null) { + return null; + } + try { + return CipherUtils.getSHA256(GcmActivity.token).substring(0, 32); + } catch (Exception e) { + UserError.Log.wtf(TAG, "Got exception in getId: " + e); + return null; + } + } + + /** + * @noinspection DataFlowIssue + */ + public static void sendMessage(Bundle input) { + + val ids = getId(); + if (ids == null) { + if (JoH.ratelimit("sendMessage error", 1200)) { + UserError.Log.wtf(TAG, "Cannot send message due to missing id"); + } + return; + } + try { + UserError.Log.d(TAG, "sendMessage called"); + InetAddress address = InetAddress.getByName(serverAddress); + + byte[] id = hexToBytes(ids); + if (id.length != 16) { + throw new RuntimeException("Invalid id length: " + id.length); + } + byte[] channel = GcmActivity.myIdentity().getBytes(StandardCharsets.UTF_8); + if (channel.length != 32) { + throw new RuntimeException("Invalid channel length: " + channel.length); + } + + byte[] type = new byte[16]; + byte[] actionb = input.getString("action").getBytes(StandardCharsets.UTF_8); + System.arraycopy(actionb, 0, type, 0, Math.min(type.length, actionb.length)); + byte[] payload = input.getString("payload").getBytes(StandardCharsets.UTF_8); + short messageSize = (short) payload.length; + + val buffer = ByteBuffer.allocate(1 + 16 + 32 + 16 + 2 + payload.length); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.put(PROTOCOL_VERSION); + buffer.put(id); + buffer.put(channel); + buffer.put(type); + buffer.putShort(messageSize); + buffer.put(payload); + + val data = buffer.array(); + val packet = new DatagramPacket(data, data.length, address, serverPort); + + try (DatagramSocket socket = new DatagramSocket()) { + socket.send(packet); + UserError.Log.d(TAG, "Message sent to server"); + } + } catch (Exception e) { + UserError.Log.e(TAG, "Error: " + e); + } + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/g5model/CalibrationState.java b/app/src/main/java/com/eveningoutpost/dexdrip/g5model/CalibrationState.java index fb5a960f5b..32299e6b26 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/g5model/CalibrationState.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/g5model/CalibrationState.java @@ -40,7 +40,7 @@ public enum CalibrationState { SensorFailedStart(0x16, "Sensor Failed Start"), SensorFailedStart2(0x17, "Sensor Failed Start 2"), SensorExpired(0x18, "Sensor Expired"), - SensorFailed7(0x19, "Sensor Failed 7"), + SensorFailed7(0x19, "Sensor Failed 7"), // apparently not a failure state SensorStopped2(0x1A, "Sensor Stopped 2"), SensorFailed8(0x1B, "Sensor Failed 8"), SensorFailed9(0x1C, "Sensor Failed 9"), @@ -58,7 +58,7 @@ public enum CalibrationState { private static final SparseArray lookup = new SparseArray<>(); private static final ImmutableSet failed = ImmutableSet.of(SensorFailed, SensorFailed2, SensorFailed3, SensorFailed4, SensorFailed5, SensorFailed6, SensorFailedStart); - private static final ImmutableSet stopped = ImmutableSet.of(Stopped, Ended, SensorFailed, SensorFailed2, SensorFailed3, SensorFailed4, SensorFailed5, SensorFailed6, SensorFailedStart, SensorStopped); + private static final ImmutableSet stopped = ImmutableSet.of(Stopped, Ended, SensorExpired, SensorFailed, SensorFailed2, SensorFailed3, SensorFailed4, SensorFailed5, SensorFailed6, SensorFailedStart, SensorStopped); private static final ImmutableSet transitional = ImmutableSet.of(WarmingUp, SensorStarted, SensorStopped, CalibrationSent); @@ -85,7 +85,8 @@ public static CalibrationState parse(int state) { public boolean usableGlucose() { return this == Ok - || this == NeedsCalibration; + || this == NeedsCalibration + || this == SensorFailed7; } public boolean insufficientCalibration() { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/g5model/Ob1G5StateMachine.java b/app/src/main/java/com/eveningoutpost/dexdrip/g5model/Ob1G5StateMachine.java index 1488d74b7c..5992a73a18 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/g5model/Ob1G5StateMachine.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/g5model/Ob1G5StateMachine.java @@ -92,6 +92,7 @@ import static com.eveningoutpost.dexdrip.utilitymodels.Constants.HOUR_IN_MS; import static com.eveningoutpost.dexdrip.utilitymodels.Constants.MINUTE_IN_MS; import static com.eveningoutpost.dexdrip.utilitymodels.Constants.SECOND_IN_MS; +import static com.eveningoutpost.dexdrip.utils.DexCollectionType.getBestCollectorHardwareName; import static com.eveningoutpost.dexdrip.utils.bt.Helper.getStatusName; @@ -133,6 +134,7 @@ public class Ob1G5StateMachine { private static volatile AuthRequestTxMessage lastAuthPacket; private static volatile boolean backup_loaded = false; private static final int OLDEST_RAW = 300 * 24 * 60 * 60; // 300 days + private static long relAutoSessionStartTime = HOUR_IN_MS * 3; public static long maxBackfillPeriod_MS = 0; @@ -1550,14 +1552,19 @@ private static void processQueueCommand(Ob1G5CollectionService parent, RxBleConn } private static void checkAndActivateSensor() { - // automagically start an xDrip sensor session if transmitter already has active sensor + // automagically start an xDrip sensor session if transmitter already has active sensor if (!Sensor.isActive() && Ob1G5CollectionService.isG5SensorStarted() && (!Sensor.stoppedRecently() || shortTxId())) { JoH.static_toast_long(xdrip.gs(R.string.auto_starting_sensor)); - // TODO possibly here we want to look at last sensor stop time and not backtrack before that - Sensor.create(tsl() - HOUR_IN_MS * 3); - if (shortTxId()) { // If we are using G7 - Sensor.create(tsl() - HOUR_IN_MS * 24); + if (shortTxId()) relAutoSessionStartTime = HOUR_IN_MS * 24; // If we are using a G7 + final List last = BgReading.latest(1); // Last reading + if ((last != null) && (last.size() > 0)) { // Have we had a reading? + final long now = JoH.tsl(); + final long since = now - last.get(0).timestamp; // Time since last reading + if (since < relAutoSessionStartTime) { // If the last reading was less than 3 hours ago, or if we are using G7 and the last reading was less than 24 hours ago + relAutoSessionStartTime = since; // We will start the new session starting from the last reading. + } } + Sensor.create(tsl() - relAutoSessionStartTime); } } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/models/JoH.java b/app/src/main/java/com/eveningoutpost/dexdrip/models/JoH.java index 584cea0e38..84114994d1 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/models/JoH.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/models/JoH.java @@ -809,14 +809,18 @@ public static String niceTimeScalar(double t, int digits) { } - public static String niceTimeScalarNatural(long t) { + public static String niceTimeScalarNatural(long t) { // Shows the integer part only when less than 1 day. + return niceTimeScalarNatural(t, 0); + } + + public static String niceTimeScalarNatural(long t, int h_digits) { // Rounds down to the defined number of decimal points when less than 1 day. if (t > 3000000) t = t + 10000; // round up by 10 seconds if nearly an hour if ((t > Constants.DAY_IN_MS) && (t < Constants.WEEK_IN_MS * 2)) { final SimpleDateFormat df = new SimpleDateFormat("EEEE", Locale.getDefault()); final String day = df.format(new Date(JoH.tsl() + t)); return ((t > Constants.WEEK_IN_MS) ? "next " : "") + day; } else { - return niceTimeScalar(t); + return niceTimeScalar(t, h_digits); } } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/models/LibreOOPAlgorithm.java b/app/src/main/java/com/eveningoutpost/dexdrip/models/LibreOOPAlgorithm.java index c6ed86d0fa..f5d51c8cab 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/models/LibreOOPAlgorithm.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/models/LibreOOPAlgorithm.java @@ -303,6 +303,7 @@ public static SensorType getSensorType(byte[] SensorInfo) { return SensorType.LibreUS14Day; case 0x9d0830: case 0xc50930: + case 0xc60931: return SensorType.Libre2; case 0x700010: return SensorType.LibreProH; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/services/Ob1G5CollectionService.java b/app/src/main/java/com/eveningoutpost/dexdrip/services/Ob1G5CollectionService.java index 70a6702cd9..ccfa545f5e 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/services/Ob1G5CollectionService.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/services/Ob1G5CollectionService.java @@ -1153,7 +1153,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { } minimize_scanning = Pref.getBooleanDefaultFalse("ob1_minimize_scanning"); - // allow_scan_by_mac = Build.VERSION.SDK_INT >= 32 && shortTxId(); + // allow_scan_by_mac = Build.VERSION.SDK_INT >= 32 && shortTxId(); automata(); // sequence logic UserError.Log.d(TAG, "Releasing service start"); @@ -1785,7 +1785,7 @@ public void instantCreateBondIfAllowed() { private boolean getInitiateBondingFlag() { - return Pref.getBoolean("ob1_initiate_bonding_flag", true); + return true; // There is no reason not to initiate bonding } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/services/UiBasedCollector.java b/app/src/main/java/com/eveningoutpost/dexdrip/services/UiBasedCollector.java index 01383d195a..3427239f87 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/services/UiBasedCollector.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/services/UiBasedCollector.java @@ -91,6 +91,7 @@ public class UiBasedCollector extends NotificationListenerService { coOptedPackages.add("com.medtronic.diabetes.simplera.eu"); coOptedPackages.add("com.senseonics.gen12androidapp"); coOptedPackages.add("com.senseonics.androidapp"); + coOptedPackages.add("com.microtech.aidexx.mgdl"); // Experiment coOptedPackagesAll.add("com.dexcom.dexcomone"); coOptedPackagesAll.add("com.dexcom.d1plus"); @@ -98,6 +99,7 @@ public class UiBasedCollector extends NotificationListenerService { coOptedPackagesAll.add("com.medtronic.diabetes.simplera.eu"); coOptedPackagesAll.add("com.senseonics.gen12androidapp"); coOptedPackagesAll.add("com.senseonics.androidapp"); + coOptedPackagesAll.add("com.microtech.aidexx.mgdl"); // Experiment companionAppIoBPackages.add("com.insulet.myblue.pdm"); diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tables/BgReadingTable.java b/app/src/main/java/com/eveningoutpost/dexdrip/tables/BgReadingTable.java index a3ffaa759d..9c7b6f3917 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tables/BgReadingTable.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tables/BgReadingTable.java @@ -62,10 +62,10 @@ public void onNavigationDrawerItemSelected(int position) { private void getData() { final List latest = BgReading.latest(5000); - parseDataForStats(latest); - ListAdapter adapter = new BgReadingAdapter(this, latest); - this.setListAdapter(adapter); try { + parseDataForStats(latest); + ListAdapter adapter = new BgReadingAdapter(this, latest); + this.setListAdapter(adapter); if (total > 0) { this.getActionBar().setSubtitle(String.format(Locale.getDefault(), "%d in 24h, bf:%d%% mis:%d", total, ((backfilled * 100) / total), missing)); } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/CompatibleApps.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/CompatibleApps.java index 50d4a43e4a..9233195ff0 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/CompatibleApps.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/CompatibleApps.java @@ -173,7 +173,7 @@ private static int notify(String short_name, String msg, int id, Feature action) public static void showNotification(String title, String content, PendingIntent yesIntent, PendingIntent noIntent, PendingIntent contentIntent, int notificationId) { - final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(xdrip.getAppContext(), null) + final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(xdrip.getAppContext(), (String)null) .setSmallIcon(R.drawable.ic_action_communication_invert_colors_on) .setContentTitle(title) .setContentText(content) diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/Constants.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/Constants.java index 160d9e8346..49cd91fc46 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/Constants.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/Constants.java @@ -58,6 +58,8 @@ public class Constants { static final int NIGHTSCOUT_ERROR_NOTIFICATION_ID = 2001; public static final int HEALTH_CONNECT_RESPONSE_ID = 2002; + public static final int ZXING_CAM_REQ_CODE = 49374; + public static final int ZXING_FILE_REQ_CODE = 49375; // This is created by just incrementing the existing camera scan code from the zxing package public static final int SENSORY_EXPIRY_NOTIFICATION_ID = 2003; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/IdempotentMigrations.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/IdempotentMigrations.java index 9344057bce..65c0c5776a 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/IdempotentMigrations.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/IdempotentMigrations.java @@ -149,6 +149,9 @@ private static void legacySettingsFix() { Pref.setBoolean("ob1_g5_fallback_to_xdrip", false); Pref.setBoolean("always_unbond_G5", false); Pref.setBoolean("always_get_new_keys", true); + Pref.setBoolean("run_ble_scan_constantly", false); + Pref.setBoolean("run_G5_ble_tasks_on_uithread", false); + Pref.setBoolean("ob1_initiate_bonding_flag", true); } private static void legacySettingsMoveLanguageFromNoToNb() { // Check if the user's language preference is set to "no" diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/Notifications.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/Notifications.java index 12500de838..ce7d28e1ee 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/Notifications.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/Notifications.java @@ -651,6 +651,8 @@ public synchronized Notification createOngoingNotification(BgGraphBuilder bgGrap if (lastReading != null) { b.setWhen(lastReading.timestamp); + b.setShowWhen(true); + final SpannableString deltaString = new SpannableString("Delta: " + ((dg != null) ? (dg.spannableString(dg.unitized_delta + (dg.from_plugin ? " "+context.getString(R.string.p_in_circle) : ""))) : bgGraphBuilder.unitizedDeltaString(true, true))); diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SaveLogs.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SaveLogs.java new file mode 100644 index 0000000000..a8a806ef68 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SaveLogs.java @@ -0,0 +1,110 @@ +package com.eveningoutpost.dexdrip.utilitymodels; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Environment; +import android.view.View; +import android.widget.TextView; + +import com.eveningoutpost.dexdrip.BaseAppCompatActivity; +import com.eveningoutpost.dexdrip.R; +import com.eveningoutpost.dexdrip.models.JoH; +import com.eveningoutpost.dexdrip.models.UserError; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import static com.eveningoutpost.dexdrip.utils.FileUtils.makeSureDirectoryExists; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +// Saves xDrip logs to storage. +// SendFeedBack sends logs to the lead developer. +// This does the same thing for saving logs to storage. +// Navid200 +// July 2024 + +public class SaveLogs extends BaseAppCompatActivity { + + private static final String TAG = "save logs"; + private String LOG_FILE_PATH = "/Download/xDrip-export"; // Path to where we save the log file + private String LOG_FILE_NAME = "xDrip-log.txt"; // Log file name + private final static int MY_PERMISSIONS_REQUEST_STORAGE = 104; + private String log_data = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_save_logs); + + Intent intent = getIntent(); + if (intent != null) { + final Bundle bundle = intent.getExtras(); + if (bundle != null) { + final String str2 = bundle.getString("generic_text"); + if (str2 != null) { + log_data = str2; + ((TextView) findViewById(R.id.yourSaveText)).setText(log_data.length() > 300 ? "\n\nAttached " + log_data.length() + " characters of log data. (hidden)\n\n" : log_data); + } + } + } + } + + public void closeActivity(View myview) { + finish(); + } + + public void saveLogs(View myview) { + if (saveLogsToStorage(log_data)) { + UserError.Log.e(TAG, "Saved log file to /Downloads/xDrip-export/xDrip-log.txt"); + } else { + UserError.Log.e(TAG, "Could not write log file"); + } + log_data = ""; + closeActivity(null); // Let's close the menu + } + + public boolean saveLogsToStorage(String contents) { + if (isStorageWritable(this, MY_PERMISSIONS_REQUEST_STORAGE)) { + try { + final StringBuilder sb = new StringBuilder(); + sb.append(Environment.getExternalStorageDirectory().getAbsolutePath()); + sb.append(LOG_FILE_PATH); + final String dir = sb.toString(); + makeSureDirectoryExists(dir); + final String pathPlusFileName = dir + "/" + LOG_FILE_NAME; + final File myExternalFile = new File(pathPlusFileName); + FileOutputStream fos = new FileOutputStream(myExternalFile); + fos.write(contents.getBytes()); + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } else { + JoH.static_toast_long("getString(R.string.sdcard_not_writable_cannot_save)"); + return false; + } + } + + public static boolean isStorageWritable(Activity context, int request_code) { // Get write permission if not & return false. Return true if yes and not tied up. + if (ContextCompat.checkSelfPermission(context, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(context, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + request_code); + UserError.Log.e(TAG, "Did not have write permission, but should have it now"); + return false; + } + String state = Environment.getExternalStorageState(); + return Environment.MEDIA_MOUNTED.equals(state); + } + +} + diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SendFeedBack.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SendFeedBack.java index 7f05385904..7b6c080af4 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SendFeedBack.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/SendFeedBack.java @@ -78,7 +78,7 @@ protected void onCreate(Bundle savedInstanceState) { final String str2 = bundle.getString("generic_text"); if (str2 != null) { log_data = str2; - ((EditText) findViewById(R.id.yourText)).setText(log_data.length() > 300 ? "\n\nPlease describe what you think these logs may show? Explain the problem if there is one.\n\nAttached " + log_data.length() + " characters of log data. (hidden)\n\n" : log_data); + ((EditText) findViewById(R.id.yourText)).setText(log_data.length() > 300 ? "\n\nPlease describe what you think these logs may show. Explain the problem if there is one.\n\nAttached " + log_data.length() + " characters of log data. (hidden)\n\n" : log_data); type_of_message = "Log Push"; myrating.setVisibility(View.GONE); ratingtext.setVisibility(View.GONE); @@ -177,7 +177,7 @@ public void sendFeedback(View myview) { try { final RequestBody formBody = new FormEncodingBuilder() .add("contact", contact.getText().toString()) - .add("body", JoH.getDeviceDetails() + "\n" + JoH.getVersionDetails() + "\n" + getBestCollectorHardwareName() + "\n===\n\n" + yourtext.getText().toString() + " \n\n===\nType: " + type_of_message + "\nLog data:\n\n" + log_data + "\n\n\nSent: " + JoH.dateTimeText(JoH.tsl())) + .add("body",yourtext.getText().toString() + " \n\n===\nType: " + type_of_message + "\nLog data:\n\n" + log_data) // Adding "Your text" and type to the log .add("rating", String.valueOf(myrating.getRating())) .add("type", type_of_message) .build(); 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 afbb490e9a..d86da4ce00 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utils/Preferences.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/Preferences.java @@ -17,6 +17,8 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Color; import android.media.Ringtone; import android.media.RingtoneManager; @@ -124,12 +126,20 @@ import com.eveningoutpost.dexdrip.webservices.XdripWebService; import com.eveningoutpost.dexdrip.xDripWidget; import com.eveningoutpost.dexdrip.xdrip; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.RGBLuminanceSource; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; import com.nightscout.core.barcode.NSBarcodeConfig; import net.tribe7.common.base.Joiner; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.lang.reflect.Method; import java.net.URI; import java.text.DecimalFormat; @@ -170,6 +180,13 @@ public class Preferences extends BasePreferenceActivity implements SearchPrefere private static AllPrefsFragment pFragment; private BroadcastReceiver mibandStatusReceiver; + // The following three variables enable us to create a common state from the input, + // whether we scan from camera or a file, and continue with the same following + // set of commands to avoid code duplication. + private volatile String scanFormat = null; // The format of the scan + private volatile String scanContents = null; // Text content of the scan coming either from camera or file + private volatile byte[] scanRawBytes = null; // Raw bytes of the scan + private void refreshFragments() { refreshFragments(null); } @@ -341,7 +358,11 @@ public static Boolean getBooleanPreferenceViaContextWithoutException(Context con @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + protected synchronized void onActivityResult(int requestCode, int resultCode, Intent data) { + // Let's reset variables just to be sure + scanFormat = null; + scanContents = null; + scanRawBytes = null; if (requestCode == Constants.HEALTH_CONNECT_RESPONSE_ID) { if (HealthConnectEntry.enabled()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -352,22 +373,64 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } } + if (requestCode == Constants.ZXING_FILE_REQ_CODE) { // If we are scanning an image file, not using the camera + // The core of the following section, selecting the file, converting it into a bitmap, and then to a bitstream, is from: + // https://stackoverflow.com/questions/55427308/scaning-qrcode-from-image-not-from-camera-using-zxing + if (data == null || data.getData() == null) { + Log.e("TAG", "No file was selected"); + return; + } + Uri uri = data.getData(); + try { + InputStream inputStream = getContentResolver().openInputStream(uri); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + if (bitmap == null) { + Log.e("TAG", "uri is not a bitmap," + uri.toString()); + return; + } + int width = bitmap.getWidth(), height = bitmap.getHeight(); + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + bitmap.recycle(); + bitmap = null; + RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels); + BinaryBitmap bBitmap = new BinaryBitmap(new HybridBinarizer(source)); + MultiFormatReader reader = new MultiFormatReader(); + try { + Result result = reader.decode(bBitmap); + scanFormat = result.getBarcodeFormat().toString(); + scanContents = result.getText(); // The text content of the scanned file + scanRawBytes = result.getRawBytes(); + } catch (NotFoundException e) { + Log.e("TAG", "decode exception", e); + } + } catch (FileNotFoundException e) { + Log.e("TAG", "can not open file" + uri.toString(), e); + } + } else if (requestCode == Constants.ZXING_CAM_REQ_CODE) { // If we are scanning from camera + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data); + scanFormat = scanResult.getFormatName(); + scanContents = scanResult.getContents(); // The text content of the scan from camera + scanRawBytes = scanResult.getRawBytes(); + } + // We now have scan format, scan text content, and scan raw bytes in the corresponding variables. + // Everything after this is applied whether we scanned with camera or from a file. - IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - if (scanResult == null || scanResult.getContents() == null) { + if (scanContents == null) { // If we have no scan content + UserError.Log.d(TAG, "No scan results "); return; } - if (scanResult.getFormatName().equals("QR_CODE")) { - final String scanresults = scanResult.getContents(); - if (QRcodeUtils.hasDecoderMarker(scanresults)) { - installxDripPlusPreferencesFromQRCode(prefs, scanresults); + if (scanFormat.equals("QR_CODE")) { // The scan is a QR code + + if (QRcodeUtils.hasDecoderMarker(scanContents)) { + installxDripPlusPreferencesFromQRCode(prefs, scanContents); return; } try { - if (BlueJay.processQRCode(scanResult.getRawBytes())) { + if (BlueJay.processQRCode(scanRawBytes)) { refreshFragments(); return; } @@ -376,7 +439,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } - final NSBarcodeConfig barcode = new NSBarcodeConfig(scanresults); + final NSBarcodeConfig barcode = new NSBarcodeConfig(scanContents); if (barcode.hasMongoConfig()) { if (barcode.getMongoUri().isPresent()) { SharedPreferences.Editor editor = prefs.edit(); @@ -427,9 +490,9 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { editor.putBoolean("cloud_storage_mqtt_enable", false); editor.apply(); } - } else if (scanResult.getFormatName().equals("CODE_128")) { - Log.d(TAG, "Setting serial number to: " + scanResult.getContents()); - prefs.edit().putString("share_key", scanResult.getContents()).apply(); + } else if (scanFormat.equals("CODE_128")) { + Log.d(TAG, "Setting serial number to: " + scanContents); + prefs.edit().putString("share_key", scanContents).apply(); } refreshFragments(); } @@ -1006,6 +1069,7 @@ public void onCreate(Bundle savedInstanceState) { addPreferencesFromResource(R.xml.pref_data_sync); setupBarcodeConfigScanner(); setupBarcodeShareScanner(); + setupQrFromFile(); bindPreferenceSummaryToValue(findPreference("cloud_storage_mongodb_uri")); bindPreferenceSummaryToValue(findPreference("cloud_storage_mongodb_collection")); bindPreferenceSummaryToValue(findPreference("cloud_storage_mongodb_device_status_collection")); @@ -1184,11 +1248,6 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { final Preference collectionMethod = findPreference("dex_collection_method"); final Preference runInForeground = findPreference("run_service_in_foreground"); final Preference g5nonraw = findPreference("g5_non_raw_method"); - final Preference g5extendedsut = findPreference("g5_extended_sut"); - final Preference scanConstantly = findPreference("run_ble_scan_constantly"); - final Preference runOnMain = findPreference("run_G5_ble_tasks_on_uithread"); - final Preference reAuth = findPreference("always_get_new_keys"); - final Preference reBond = findPreference("always_unbond_G5"); final Preference wifiRecievers = findPreference("wifi_recievers_addresses"); final Preference predictiveBG = findPreference("predictive_bg"); final Preference interpretRaw = findPreference("interpret_raw"); @@ -1275,6 +1334,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { final Preference shFollowUser = findPreference("shfollow_user"); final Preference shFollowPass = findPreference("shfollow_pass"); + final Preference shFollowServerUS = findPreference("dex_share_us_acct"); if (collectionType == DexCollectionType.SHFollow) { final Preference.OnPreferenceChangeListener shFollowListener = new Preference.OnPreferenceChangeListener() { @@ -1289,6 +1349,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { try { shFollowUser.setOnPreferenceChangeListener(shFollowListener); shFollowPass.setOnPreferenceChangeListener(shFollowListener); + shFollowServerUS.setOnPreferenceChangeListener(shFollowListener); } catch (Exception e) { // } @@ -1297,6 +1358,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { try { collectionCategory.removePreference(shFollowUser); collectionCategory.removePreference(shFollowPass); + collectionCategory.removePreference(shFollowServerUS); } catch (Exception e) { // } @@ -2522,6 +2584,8 @@ private void removeLegacyPreferences() { // removePreferenceFromCategory("ob1_g5_fallback_to_xdrip", "ob1_options"); // removePreferenceFromCategory("always_unbond_G5", "ob1_options"); // removePreferenceFromCategory("always_get_new_keys", "ob1_options"); + // removePreferenceFromCategory("run_ble_scan_constantly", "ob1_options"); + // removePreferenceFromCategory("run_G5_ble_tasks_on_uithread", "ob1_options"); } private void removePreferenceFromCategory(final String preference, final String category) { @@ -2853,6 +2917,16 @@ public boolean onPreferenceClick(Preference preference) { }); } + private void setupQrFromFile() { + findPreference("qr_code_from_file").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { // Listener for scanning QR code from file + new QrCodeFromFile(getActivity()).scanFile(); + return true; + } + }); + } + private void refresh_extra_items() { try { if (this.prefs == null) return; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/QrCodeFromFile.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/QrCodeFromFile.java new file mode 100644 index 0000000000..c275d5dc76 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/QrCodeFromFile.java @@ -0,0 +1,67 @@ +package com.eveningoutpost.dexdrip.utils; + +import android.app.Activity; +import android.content.Intent; + +import com.eveningoutpost.dexdrip.models.UserError; +import com.eveningoutpost.dexdrip.utilitymodels.Constants; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * This is a helper class to manage QR code scan from file and + * return results to the instantiating activity to complement the existing scan from camera function. + * The scan from file portion reference: https://stackoverflow.com/questions/55427308/scaning-qrcode-from-image-not-from-camera-using-zxing + */ + +public class QrCodeFromFile { + private static final String TAG = QrCodeFromFile.class.getSimpleName(); + + private Activity activity; + private Collection desiredBarcodeFormats; + + + public QrCodeFromFile(Activity activity) { + this.activity = activity; + } + + public QrCodeFromFile setDesiredBarcodeFormats(Collection desiredBarcodeFormats) { + this.desiredBarcodeFormats = desiredBarcodeFormats; + return this; + } + + public final void initiateFileScan() { + UserError.Log.e(TAG, "Navid_ initiate scan"); + + // TODO Replace startActivityForResult with Androidx Activity Result APIs + this.activity.startActivityForResult(this.createFileScanIntent(), Constants.ZXING_FILE_REQ_CODE); + + } + + public Intent createFileScanIntent() { + Intent pickIntent = new Intent(Intent.ACTION_PICK); + pickIntent.setDataAndType( android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); + + return pickIntent; + } + + private static List list(String... values) { + return Collections.unmodifiableList(Arrays.asList(values)); + } + + public void scanFile() { // Copied (and slightly modified) from AndroidBarcode.scan() + UserError.Log.e(TAG, "Navid_ scanFile "); + actuallyStartScanFile(); + } + + private void actuallyStartScanFile() { + UserError.Log.e(TAG, "Navid_ actuallyScan "); + new QrCodeFromFile(activity) + .setDesiredBarcodeFormats(list("QR_CODE", "CODE_128")) + .initiateFileScan(); + } + +} diff --git a/app/src/main/res/layout/activity_event_log.xml b/app/src/main/res/layout/activity_event_log.xml index 71e522b53e..a65bb8d699 100644 --- a/app/src/main/res/layout/activity_event_log.xml +++ b/app/src/main/res/layout/activity_event_log.xml @@ -139,6 +139,17 @@ android:layout_weight="1" android:onClick="uploadEventLogs" android:text="@string/upload_logs" + android:textAllCaps="false" + android:textAlignment="center" /> + +