diff --git a/.travis.yml b/.travis.yml index 6799bbf35f..52552b3ceb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,4 +15,7 @@ android: script: # Unit Test - - ./gradlew test \ No newline at end of file + - ./gradlew test jacocoTestReport + +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/README-Combo.md b/README-Combo.md index 86ad5a73b2..a13f5af94c 100644 --- a/README-Combo.md +++ b/README-Combo.md @@ -11,7 +11,10 @@ Hardware requirements: Software to configure the pump. Roche sends out Smartpix devices and the configuration software free of charge to their customers upon request. -- A compatible phone: An Android phone with a phone running LineageOS 14.1 (formerly CyanogenMod) or Android 8.1 (Oreo) +- A compatible phone: An Android phone with a phone running LineageOS 14.1 (formerly CyanogenMod) or Android 8.1 (Oreo). + For advanced users, it is possible to perform the pairing on a rooted phone and transfer it to another rooted + phone to use with ruffy/AAPS, which must also be rooted. This allows using phones with Android < 8.1 but + has not been widely tested: https://github.com/gregorybel/combo-pairing/blob/master/README.md - To build AndroidAPS with Combo support you need the latest Android Studio 3 version Limitations: @@ -57,15 +60,17 @@ Setup: - Enable keylock (can also be set on the pump directly, see usage section on reasoning) - Get Android Studio 3 https://developer.android.com/studio/index.html - Follow the link http://ruffy.AndroidAPS.org and clone via git (branch `combo-scripter-v2`) -- Pair the pump using ruffy, if it doesn't work after multiple attempts, switch to the `pairing` branch, pair, - then switch back the original branch. If the pump is already paired and - can be controlled via ruffy, installing the above version is sufficient. +- Pair the pump using ruffy. If it doesn't work after multiple attempts, switch to the `pairing` branch, + pair the pump and then switch back the original branch. + If the pump is already paired and can be controlled via ruffy, installing the + `combo-scripter-v2` branch is sufficient. If AAPS is already installed, switch to the MDI plugin to avoid the Combo plugin from interfering with ruffy during the pairing process. Note that the pairing processing is somewhat fragile (but only has to be done once) - and may need a few attempts; - quickly acknowledge prompts and when starting over, remove the pump device - from the bluetooth settings beforehand. + and may need a few attempts; quickly acknowledge prompts and when starting over, remove the pump device + from the bluetooth settings beforehand. Another option to try is to go to the bluetooth menu after + initiating the pairing process (this keeps the phone's bluetooth discoverable as long as the menu is displayed) + and switch back to ruffy after confirming the pairing on the pump, when the pump displays the authorization code. When AAPS is using ruffy, the ruffy app can't be used. The easiest way is to just reboot the phone after the pairing process and let AAPS start ruffy in the background. - Clone AndroidAPS from https://github.com/jotomo/AndroidAPS (branch `combo-scripter-v2`) diff --git a/README.md b/README.md index b700f18b7a..54d9bb092a 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,5 @@ [![Gitter](https://badges.gitter.im/MilosKozak/AndroidAPS.svg)](https://gitter.im/MilosKozak/AndroidAPS?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build status](https://travis-ci.org/MilosKozak/AndroidAPS.svg?branch=master)](https://travis-ci.org/MilosKozak/AndroidAPS) +[![codecov](https://codecov.io/gh/MilosKozak/AndroidAPS/branch/master/graph/badge.svg)](https://codecov.io/gh/MilosKozak/AndroidAPS) +dev: [![codecov](https://codecov.io/gh/MilosKozak/AndroidAPS/branch/dev/graph/badge.svg)](https://codecov.io/gh/MilosKozak/AndroidAPS) \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d6b4f914f6..e4ddb5ef71 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,25 @@ buildscript { repositories { maven { url 'https://maven.fabric.io/public' } + jcenter() } dependencies { classpath 'io.fabric.tools:gradle:1.+' + classpath 'com.dicedmelon.gradle:jacoco-android:0.1.2' } } -apply plugin: 'com.android.application' -apply plugin: 'io.fabric' +apply plugin: "com.android.application" +apply plugin: "io.fabric" +apply plugin: "jacoco-android" + +ext { + supportLibraryVersion = "23.4.0" + ormLiteVersion = "4.46" + powermockVersion = "1.7.3" + dexmakerVersion = "1.2" +} + repositories { maven { url 'https://maven.fabric.io/public' } @@ -55,12 +66,16 @@ android { } lintOptions { disable 'MissingTranslation' + disable 'ExtraTranslation' } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + debug { + testCoverageEnabled true + } } productFlavors { flavorDimensions "standard" @@ -147,56 +162,58 @@ allprojects { dependencies { wearApp project(':wear') - - compile fileTree(include: ['*.jar'], dir: 'libs') - compile('com.crashlytics.sdk.android:crashlytics:2.6.7@aar') { - transitive = true; - } - compile('com.crashlytics.sdk.android:answers:1.3.12@aar') { - transitive = true; - } - - compile 'com.android.support:appcompat-v7:23.4.0' - compile 'com.android.support:support-v4:23.4.0' - compile 'com.android.support:cardview-v7:23.4.0' - compile 'com.android.support:recyclerview-v7:23.4.0' - compile 'com.android.support:gridlayout-v7:23.4.0' - compile "com.android.support:design:23.4.0" - compile "com.android.support:percent:23.4.0" - compile 'com.wdullaer:materialdatetimepicker:2.3.0' - compile 'com.squareup:otto:1.3.7' - compile 'com.j256.ormlite:ormlite-core:4.46' - compile 'com.j256.ormlite:ormlite-android:4.46' - compile('com.github.tony19:logback-android-classic:1.1.1-6') { - exclude group: 'com.google.android', module: 'android' - } - compile 'org.apache.commons:commons-lang3:3.6' - compile 'org.slf4j:slf4j-api:1.7.12' - compile 'com.jjoe64:graphview:4.0.1' - compile 'com.joanzapata.iconify:android-iconify-fontawesome:2.1.1' - compile 'com.google.android.gms:play-services-wearable:7.5.0' - compile 'junit:junit:4.12' - testCompile 'org.json:json:20140107' - testCompile 'org.mockito:mockito-core:2.7.22' - testCompile 'org.powermock:powermock-api-mockito2:1.7.3' - testCompile 'org.powermock:powermock-module-junit4-rule-agent:1.7.3' - testCompile 'org.powermock:powermock-module-junit4-rule:1.7.3' - testCompile 'org.powermock:powermock-module-junit4:1.7.3' - androidTestCompile 'org.mockito:mockito-core:2.7.22' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - compile(name: 'android-edittext-validator-v1.3.4-mod', ext: 'aar') - compile('com.google.android:flexbox:0.3.0') { - exclude group: 'com.android.support' - } - compile('io.socket:socket.io-client:0.8.3') { - // excluding org.json which is provided by Android - exclude group: 'org.json', module: 'json' - } - compile 'com.google.code.gson:gson:2.7' - compile 'com.google.guava:guava:20.0' compile project(path: ':ruffyscripter') - compile 'net.danlew:android.joda:2.9.9.1' - testCompile 'joda-time:joda-time:2.9.4.2' + compile fileTree(include: ['*.jar'], dir: 'libs') + compile("com.crashlytics.sdk.android:crashlytics:2.6.7@aar") { + transitive = true; + } + compile("com.crashlytics.sdk.android:answers:1.3.12@aar") { + transitive = true; + } + + compile "com.android.support:appcompat-v7:${supportLibraryVersion}" + compile "com.android.support:support-v4:${supportLibraryVersion}" + compile "com.android.support:cardview-v7:${supportLibraryVersion}" + compile "com.android.support:recyclerview-v7:${supportLibraryVersion}" + compile "com.android.support:gridlayout-v7:${supportLibraryVersion}" + compile "com.android.support:design:${supportLibraryVersion}" + compile "com.android.support:percent:${supportLibraryVersion}" + compile "com.wdullaer:materialdatetimepicker:2.3.0" + compile "com.squareup:otto:1.3.7" + compile "com.j256.ormlite:ormlite-core:${ormLiteVersion}" + compile "com.j256.ormlite:ormlite-android:${ormLiteVersion}" + compile("com.github.tony19:logback-android-classic:1.1.1-6") { + exclude group: "com.google.android", module: "android" + } + compile "org.apache.commons:commons-lang3:3.6" + compile "org.slf4j:slf4j-api:1.7.12" + compile "com.jjoe64:graphview:4.0.1" + compile "com.joanzapata.iconify:android-iconify-fontawesome:2.1.1" + compile "com.google.android.gms:play-services-wearable:7.5.0" + compile(name: "android-edittext-validator-v1.3.4-mod", ext: "aar") + compile("com.google.android:flexbox:0.3.0") { + exclude group: "com.android.support" + } + compile("io.socket:socket.io-client:0.8.3") { + // excluding org.json which is provided by Android + exclude group: "org.json", module: "json" + } + compile "com.google.code.gson:gson:2.7" + compile "com.google.guava:guava:20.0" + + compile "net.danlew:android.joda:2.9.9.1" + + testCompile "junit:junit:4.12" + testCompile "org.json:json:20140107" + testCompile "org.mockito:mockito-core:2.7.22" + testCompile "org.powermock:powermock-api-mockito2:${powermockVersion}" + testCompile "org.powermock:powermock-module-junit4-rule-agent:${powermockVersion}" + testCompile "org.powermock:powermock-module-junit4-rule:${powermockVersion}" + testCompile "org.powermock:powermock-module-junit4:${powermockVersion}" + testCompile "joda-time:joda-time:2.9.4.2" + + androidTestCompile "org.mockito:mockito-core:2.7.22" + androidTestCompile "com.google.dexmaker:dexmaker:${dexmakerVersion}" + androidTestCompile "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}" } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java index 03f0f60341..62848b7e5e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java @@ -631,7 +631,7 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr @Override @Nullable public TemporaryBasal getTempBasalFromHistory(long time) { - return activeTreatments.getTempBasalFromHistory(time); + return activeTreatments != null ? activeTreatments.getTempBasalFromHistory(time) : null; } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java index dc67a1449d..9e631def18 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java @@ -139,8 +139,8 @@ public class PersistentNotificationPlugin implements PluginBase { } } - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { - TemporaryBasal activeTemp = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal activeTemp = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { line1 += " " + activeTemp.toStringShort(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java index 506bce7be5..8419478725 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java @@ -35,8 +35,6 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis private Button alertsButton; private Button tddsButton; private Button fullHistoryButton; - private TextView queueView; - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -49,7 +47,6 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis reservoirView = (TextView) view.findViewById(R.id.combo_insulinstate); lastConnectionView = (TextView) view.findViewById(R.id.combo_lastconnection); tempBasalText = (TextView) view.findViewById(R.id.combo_temp_basal); - queueView = (TextView) view.findViewById(R.id.combo_queue); refreshButton = (Button) view.findViewById(R.id.combo_refresh_button); refreshButton.setOnClickListener(this); @@ -210,16 +207,6 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis } } tempBasalText.setText(tbrStr); - - // TODO clean up & i18n or remove - // Queued activities - Spanned status = ConfigBuilderPlugin.getCommandQueue().spannedStatus(); - if (status.toString().equals("")) { - queueView.setVisibility(View.GONE); - } else { - queueView.setVisibility(View.VISIBLE); - queueView.setText("Queued activities:\n" + status); - } } }); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java index 8f0dca4996..1cd45d9f03 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java @@ -403,8 +403,10 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { @Override public double getTempBasalRemainingMinutesFromHistory() { - if (isTempBasalInProgress()) - return getTempBasalFromHistory(System.currentTimeMillis()).getPlannedRemainingMinutes(); + TemporaryBasal activeTemp = getTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { + return activeTemp.getPlannedRemainingMinutes(); + } return 0; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/XDripStatusline/StatuslinePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/XDripStatusline/StatuslinePlugin.java index 60d784d377..5fb902c2eb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/XDripStatusline/StatuslinePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/XDripStatusline/StatuslinePlugin.java @@ -182,10 +182,9 @@ public class StatuslinePlugin implements PluginBase { //Temp basal TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); - if (treatmentsInterface.isTempBasalInProgress()) { - TemporaryBasal activeTemp = treatmentsInterface.getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal activeTemp = treatmentsInterface.getTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { status += activeTemp.toStringShort(); - } //IOB diff --git a/app/src/main/res/layout/combopump_fragment.xml b/app/src/main/res/layout/combopump_fragment.xml index ecbaf90ef6..7fb016bb83 100644 --- a/app/src/main/res/layout/combopump_fragment.xml +++ b/app/src/main/res/layout/combopump_fragment.xml @@ -336,14 +336,6 @@ android:layout_marginRight="20dp" android:layout_marginTop="5dp" android:background="@color/listdelimiter" /> - - - diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8edea19afe..33a4b1bed4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -716,9 +716,9 @@ Fehlerprotokol Status Keine Verbindung zur Pumpe - Durch Benutzer gestoppt - Wegen Fehler gestoppt - Normaler Betrieb + Gestoppt (Benutzer) + Gestoppt (Fehler) + In Betrieb TDDS Bolusabgabe wird vorbereitet TBR wird abgebrochen @@ -760,6 +760,7 @@ Um die TDD-Statistik der Pumpe zu lesen, drücken Sie den TDDS Knopf lange.\nWARNUNG: Es gibt einen bekannten Fehler in der Pumpe der dazu führt, dass die Pumpe nach dieser Aktion erst wieder Verbindungen annimmt, wenn auf der Pumpe selbst ein Konpf gedrückt wird. Aus diesem Grund sollte diese Aktion nicht durchgeführt werden. Dies wird den gesamten Speicher und den Status der Pumpe auslesen sowie alle Einträge in „Meine Daten“ und die Basalrate. Boli und TBR werden unter Behandlungen gespeichert, sofern sie nicht bereits vorhanden sind. Dies kann zu doppelten Einträgen führen, wenn die Uhrzeit der Pumpe abweicht. Das Auslesen des Speichers ist normaler Weise für das Loopen unnötig und nur für besondere Umstände vorgesehen. Wenn Du es dennoch tun willst, drücke noch einmal länger den Button. ACHTUNG: Dies kann einen Fehler auslösen, der dazu führt, dass die Pumpe keine Verbindungsversuche mehr akzeptiert. Erst die Betätigung einer Taste an der Pumpe beendet diesen Zustand. Nach Möglichkeit sollte daher das Auslesen vermieden werden. Möchtest Du wirklich den gesamten Speicher der Pumpe auslesen und die möglichen Konsequenzen des Vorgangs tragen? + Nicht mehr genug Insulin im Reservoir für den Bolus Ja Nein BZ Berechnung diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 100d35e2d4..27d56f524e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -812,8 +812,6 @@ Basal values not aligned to hours Zero value in profile Received profile switch from NS but profile doesn\'t exist localy - Stopping bolus delivery - Bolus delivery stopped Programming pump for bolusing Refresh TDDS @@ -831,8 +829,6 @@ Setting TBR (%d%% / %d min) Bolusing (%.1f U) Refreshing - raise_urgent_alarms_as_android_notification - Use system notifications for alerts Never Requested operation not supported by pump Unsafe usage: extended or multiwave boluses are active. Loop mode has been set to low-suspend only 6 hours. Only normal boluses are supported in loop mode @@ -848,7 +844,6 @@ The pump is showing the error E%d: %s To read the pump\'s error history, long press the ALERTS button\n\nWARNING: this can trigger a bug which causes the pump to reject all connection attempts and requires pressing a button on the pump to recover and should therefore be avoided. To read the pump\'s TDD history, long press the TDDS button\n\nWARNING: this can trigger a bug which causes the pump to reject all connection attempts and requires pressing a button on the pump to recover and should therefore be avoided. - sync_profile_to_pump Minimum: %3.1f U Average: %3.1f U Maximum: %3.1f U @@ -865,5 +860,6 @@ Only %.2f U of the requested bolus of %.2f U was delivered due to an error. Please check the pump to verify this and take appropriate actions. Delivering the bolus and verifying the pump\'s history failed, please check the pump and manually create a bolus record using the Careportal tab if a bolus was delivered. Recovering from connection loss + Not enough insulin for bolus left in reservoir diff --git a/app/src/test/java/info/nightscout/utils/RoundTest.java b/app/src/test/java/info/nightscout/utils/RoundTest.java index d95ab2b391..1a801d2a7d 100644 --- a/app/src/test/java/info/nightscout/utils/RoundTest.java +++ b/app/src/test/java/info/nightscout/utils/RoundTest.java @@ -14,18 +14,21 @@ public class RoundTest { public void roundToTest() throws Exception { assertEquals( 0.55d, Round.roundTo(0.54d, 0.05d), 0.00000001d ); assertEquals( 1d, Round.roundTo(1.49d, 1d), 0.00000001d ); + assertEquals( 0d, Round.roundTo(0d, 1d), 0.00000001d ); } @Test public void floorToTest() throws Exception { assertEquals( 0.5d, Round.floorTo(0.54d, 0.05d), 0.00000001d ); assertEquals( 1d, Round.floorTo(1.59d, 1d), 0.00000001d ); + assertEquals( 0d, Round.floorTo(0d, 1d), 0.00000001d ); } @Test public void ceilToTest() throws Exception { assertEquals( 0.6d, Round.ceilTo(0.54d, 0.1d), 0.00000001d ); assertEquals( 2d, Round.ceilTo(1.49999d, 1d), 0.00000001d ); + assertEquals( 0d, Round.ceilTo(0d, 1d), 0.00000001d ); } } \ No newline at end of file diff --git a/wear/build.gradle b/wear/build.gradle index 0f98aff25d..7c7e2d7c49 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -1,5 +1,10 @@ apply plugin: 'com.android.application' +ext { + supportLibraryVersion = "23.0.1" + wearableVersion = "2.0.1" +} + def generateGitBuild = { -> StringBuilder stringBuilder = new StringBuilder(); @@ -60,12 +65,12 @@ allprojects { dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') - //compile 'com.ustwo.android:clockwise-wearable:1.0.2' - provided 'com.google.android.wearable:wearable:2.0.1' - compile 'com.google.android.support:wearable:2.0.1' - compile 'com.google.android.gms:play-services-wearable:7.3.0' - compile files('libs/hellocharts-library-1.5.5.jar') - compile(name:'ustwo-clockwise-debug', ext:'aar') - compile 'com.android.support:support-v4:23.0.1' - compile 'me.denley.wearpreferenceactivity:wearpreferenceactivity:0.5.0' + compile files("libs/hellocharts-library-1.5.5.jar") + //compile "com.ustwo.android:clockwise-wearable:1.0.2" + provided "com.google.android.wearable:wearable:${wearableVersion}" + compile "com.google.android.support:wearable:${wearableVersion}" + compile "com.google.android.gms:play-services-wearable:7.3.0" + compile(name:"ustwo-clockwise-debug", ext:"aar") + compile "com.android.support:support-v4:23.0.1" + compile "me.denley.wearpreferenceactivity:wearpreferenceactivity:0.5.0" } diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java index 06f50edfb0..3549b2a67d 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java @@ -178,7 +178,11 @@ public class Steampunk extends BaseWatchFace { gridColor = ContextCompat.getColor(getApplicationContext(), R.color.grey_steampunk); basalBackgroundColor = ContextCompat.getColor(getApplicationContext(), R.color.basal_dark); basalCenterColor = ContextCompat.getColor(getApplicationContext(), R.color.basal_dark); - pointSize = 1; + if (Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3")) < 3) { + pointSize = 2; + } else { + pointSize = 1; + } setupCharts(); } @@ -237,7 +241,12 @@ public class Steampunk extends BaseWatchFace { private void changeChartTimeframe() { int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3")); timeframe = (timeframe%5) + 1; + if (timeframe < 3) { + pointSize = 2; + } else { + pointSize = 1; + } + setupCharts(); sharedPrefs.edit().putString("chart_timeframe", "" + timeframe).commit(); } - } \ No newline at end of file