diff --git a/android-obd-simulator/build.gradle b/android-obd-simulator/build.gradle index 41c0d8782..9ec424eac 100644 --- a/android-obd-simulator/build.gradle +++ b/android-obd-simulator/build.gradle @@ -5,8 +5,8 @@ dependencies { } android { - compileSdkVersion 22 - buildToolsVersion "22.0.1" + compileSdkVersion 23 + buildToolsVersion "23.0.2" sourceSets { main { diff --git a/build.gradle b/build.gradle index 5f1b69ce4..00445fc13 100644 --- a/build.gradle +++ b/build.gradle @@ -39,18 +39,18 @@ ext { minSdkVersion = 16 compileSdkVersion = 23 targetSdkVersion = 23 - buildToolsVersion = '23.0.1' + buildToolsVersion = '23.0.2' versionCode = 25 versionName = "0.20.1" javaCompileVersion = JavaVersion.VERSION_1_8 // Android dependencies. - supportV4 = 'com.android.support:support-v4:23.0.1' - supportV7 = 'com.android.support:appcompat-v7:23.0.1' - supportDesign = 'com.android.support:design:23.0.1' - supportCardview = 'com.android.support:cardview-v7:23.0.1' - supportRecyclerview = 'com.android.support:recyclerview-v7:23.0.1' + supportV4 = 'com.android.support:support-v4:23.1.1' + supportV7 = 'com.android.support:appcompat-v7:23.1.1' + supportDesign = 'com.android.support:design:23.1.1' + supportCardview = 'com.android.support:cardview-v7:23.1.1' + supportRecyclerview = 'com.android.support:recyclerview-v7:23.1.1' // Dependency injection, view injection, event bus... dagger = 'com.squareup.dagger:dagger:1.2.2' diff --git a/org.envirocar.algorithm/.gitignore b/org.envirocar.algorithm/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/org.envirocar.algorithm/.gitignore @@ -0,0 +1 @@ +/build diff --git a/org.envirocar.algorithm/build.gradle b/org.envirocar.algorithm/build.gradle new file mode 100644 index 000000000..6e69bc1ce --- /dev/null +++ b/org.envirocar.algorithm/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.library' +apply plugin: 'com.neenbedankt.android-apt' +apply plugin: 'me.tatarka.retrolambda' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + compileOptions { + sourceCompatibility rootProject.ext.javaCompileVersion + targetCompatibility rootProject.ext.javaCompileVersion + } + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + testCompile 'junit:junit:4.12' + androidTestCompile 'junit:junit:4.12' + compile supportV7 + + compile rootProject.ext.dagger + apt rootProject.ext.daggerCompiler + + compile rootProject.ext.rxAndroid + compile rootProject.ext.rxJava + + compile project(':org.envirocar.obd') +} diff --git a/org.envirocar.algorithm/proguard-rules.pro b/org.envirocar.algorithm/proguard-rules.pro new file mode 100644 index 000000000..a28561fdf --- /dev/null +++ b/org.envirocar.algorithm/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/matthes/opt/adt/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/org.envirocar.algorithm/src/androidTest/java/org/envirocar/algorithm/ApplicationTest.java b/org.envirocar.algorithm/src/androidTest/java/org/envirocar/algorithm/ApplicationTest.java new file mode 100644 index 000000000..2f86b6ad5 --- /dev/null +++ b/org.envirocar.algorithm/src/androidTest/java/org/envirocar/algorithm/ApplicationTest.java @@ -0,0 +1,13 @@ +package org.envirocar.algorithm; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/org.envirocar.algorithm/src/main/AndroidManifest.xml b/org.envirocar.algorithm/src/main/AndroidManifest.xml new file mode 100644 index 000000000..3f419c200 --- /dev/null +++ b/org.envirocar.algorithm/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/AbstractMeasurementProvider.java b/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/AbstractMeasurementProvider.java new file mode 100644 index 000000000..89d915a21 --- /dev/null +++ b/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/AbstractMeasurementProvider.java @@ -0,0 +1,27 @@ +package org.envirocar.algorithm; + +import org.envirocar.core.entity.Measurement; +import org.envirocar.obd.commands.PID; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class AbstractMeasurementProvider implements MeasurementProvider { + + private List positionBuffer = new ArrayList<>(); + + @Override + public synchronized void newPosition(Position pos) { + this.positionBuffer.add(pos); + } + + public synchronized List getAndClearPositionBuffer() { + List result = Collections.unmodifiableList(positionBuffer); + positionBuffer = new ArrayList<>(result.size()); + return result; + } + +} diff --git a/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/InterpolationMeasurementProvider.java b/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/InterpolationMeasurementProvider.java new file mode 100644 index 000000000..6581692d7 --- /dev/null +++ b/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/InterpolationMeasurementProvider.java @@ -0,0 +1,303 @@ +package org.envirocar.algorithm; + +import android.location.Location; + +import com.squareup.otto.Subscribe; + +import org.envirocar.core.entity.Measurement; +import org.envirocar.core.entity.MeasurementImpl; +import org.envirocar.core.events.gps.GpsDOP; +import org.envirocar.core.events.gps.GpsDOPEvent; +import org.envirocar.core.events.gps.GpsLocationChangedEvent; +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.events.PropertyKeyEvent; +import org.envirocar.obd.events.Timestamped; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import rx.Observable; +import rx.Subscriber; + +/** + * TODO JavaDoc + */ +public class InterpolationMeasurementProvider extends AbstractMeasurementProvider { + private static final Logger LOG = Logger.getLogger(InterpolationMeasurementProvider.class); + + private Map> bufferedResponses = new + HashMap<>(); + private long firstTimestampToBeConsidered; + private long lastTimestampToBeConsidered; + + /* + * TODO implement listing for GPS DOP Events + */ + @Override + public Observable measurements(long samplingRate) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + LOG.info("measurements(): start collecting data"); + subscriber.onStart(); + + while (!subscriber.isUnsubscribed()) { + synchronized (InterpolationMeasurementProvider.this) { + /** + * wait the sampling rate + */ + try { + InterpolationMeasurementProvider.this.wait(samplingRate); + } catch (InterruptedException e) { + subscriber.onError(e); + } + + Measurement m = createMeasurement(); + + if (m != null && m.getLatitude() != null && m.getLongitude() != null + && m.hasProperty(Measurement.PropertyKey.SPEED)) { + subscriber.onNext(m); + } + } + } + LOG.info("measurements(): finished the collection of data."); + subscriber.onCompleted(); + } + }); + } + + private synchronized Measurement createMeasurement() { + /** + * use the middle of the time window + */ + long targetTimestamp = firstTimestampToBeConsidered + ((lastTimestampToBeConsidered - + firstTimestampToBeConsidered) / 2); + + Measurement m = new MeasurementImpl(); + m.setTime(targetTimestamp); + + for (Measurement.PropertyKey pk : this.bufferedResponses.keySet()) { + appendToMeasurement(pk, this.bufferedResponses.get(pk), m); + } + + /** + * clear the buffer of DataResponses to be considered + */ + clearBuffer(); + setPosition(m, getAndClearPositionBuffer()); + + return m; + } + + private void setPosition(Measurement m, List positionBuffer) { + if (positionBuffer == null || positionBuffer.isEmpty()) { + return; + } + + if (positionBuffer.size() == 1) { + Position pos = positionBuffer.get(0); + m.setLatitude(pos.getLatitude()); + m.setLongitude(pos.getLongitude()); + } else { + long targetTimestamp = m.getTime(); + + /** + * find the closest two measurements + */ + int startIndex = findStartIndex(positionBuffer, targetTimestamp); + Position start = positionBuffer.get(startIndex); + Position end = startIndex + 1 < positionBuffer.size() ? positionBuffer.get(startIndex + + 1) : null; + + double lat = interpolateTwo(start.getLatitude(), end != null ? end.getLatitude() : + null, targetTimestamp, start.getTimestamp(), + end != null ? end.getTimestamp() : 0L); + double lon = interpolateTwo(start.getLongitude(), end != null ? end.getLongitude() : + null, targetTimestamp, start.getTimestamp(), + end != null ? end.getTimestamp() : 0L); + + m.setLatitude(lat); + m.setLongitude(lon); + } + + } + + private void appendToMeasurement(Measurement.PropertyKey pk, List + dataResponses, Measurement m) { + if (pk == null) { + return; + } + + switch (pk) { + case FUEL_SYSTEM_STATUS_CODE: + m.setProperty(pk, first(dataResponses)); + break; + default: + m.setProperty(pk, interpolate(dataResponses, m.getTime())); + break; + } + + } + + private Double first(List dataResponses) { + return dataResponses.isEmpty() ? null : dataResponses.get(0).getValue().doubleValue(); + } + + protected Double interpolate(List dataResponses, long targetTimestamp) { + if (dataResponses.size() <= 1) { + return first(dataResponses); + } + + /** + * find the closest two measurements + */ + int startIndex = findStartIndex(dataResponses, targetTimestamp); + PropertyKeyEvent start = dataResponses.get(startIndex); + PropertyKeyEvent end = startIndex + 1 < dataResponses.size() ? dataResponses.get + (startIndex + 1) : null; + + return interpolateTwo(start.getValue(), end != null ? end.getValue() : null, + targetTimestamp, start.getTimestamp(), + end != null ? end.getTimestamp() : 0L); + } + + private int findStartIndex(List dataResponses, long targetTimestamp) { + int i = 0; + while (i + 1 < dataResponses.size()) { + if (dataResponses.get(i).getTimestamp() <= targetTimestamp + && dataResponses.get(i + 1).getTimestamp() >= targetTimestamp) { + return i; + } + + i++; + } + + return 0; + } + + /** + * @param start the start value + * @param end the end value + * @param targetTimestamp the target timestamp used for interpolation + * @param startTimestamp the timestamp of the start + * @param endTimestamp the timestamp of the lend + * @return the interpolated value + */ + protected Double interpolateTwo(Number start, Number end, long targetTimestamp, + long startTimestamp, long endTimestamp) { + if (start == null && end == null) { + return null; + } + if (start == null) { + return end.doubleValue(); + } else if (end == null) { + return start.doubleValue(); + } + + float duration = (float) (endTimestamp - startTimestamp); + + float endWeight = (targetTimestamp - startTimestamp) / duration; + float startWeight = (endTimestamp - targetTimestamp) / duration; + + return start.doubleValue() * startWeight + end.doubleValue() * endWeight; + } + + private void clearBuffer() { + for (List drl : this.bufferedResponses.values()) { + drl.clear(); + } + + /** + * reset the first timestamp + */ + this.firstTimestampToBeConsidered = 0; + } + + @Override + @Subscribe + public synchronized void consider(PropertyKeyEvent pke) { + updateTimestamps(pke); + + Measurement.PropertyKey pk = pke.getPropertyKey(); + + if (pk == null) { + return; + } + + if (bufferedResponses.containsKey(pk)) { + bufferedResponses.get(pk).add(pke); + } else { + List list = new ArrayList<>(); + list.add(pke); + bufferedResponses.put(pk, list); + } + } + + @Override + public synchronized void newPosition(Position pos) { + super.newPosition(pos); + updateTimestamps(pos); + } + + @Subscribe + public void newLocation(GpsLocationChangedEvent loc) { + Location location = loc.mLocation; + long now = System.currentTimeMillis(); + + newPosition(new Position(now, + location.getLatitude(), location.getLongitude())); + + if (location.hasAccuracy()) { + consider(new PropertyKeyEvent(Measurement.PropertyKey.GPS_ACCURACY, location + .getAccuracy(), now)); + } + + if (location.hasAltitude()) { + consider(new PropertyKeyEvent(Measurement.PropertyKey.GPS_ALTITUDE, location + .getAltitude(), now)); + } + + if (location.hasBearing()) { + consider(new PropertyKeyEvent(Measurement.PropertyKey.GPS_BEARING, location + .getBearing(), now)); + } + + if (location.hasSpeed()) { + consider(new PropertyKeyEvent( + Measurement.PropertyKey.GPS_SPEED, location.getSpeed() * 3.6f, now)); + } + } + + private void updateTimestamps(Timestamped dr) { + this.lastTimestampToBeConsidered = Math.max(this.lastTimestampToBeConsidered, dr + .getTimestamp()); + + if (this.firstTimestampToBeConsidered == 0) { + this.firstTimestampToBeConsidered = dr.getTimestamp(); + } else { + this.firstTimestampToBeConsidered = Math.min(this.firstTimestampToBeConsidered, dr + .getTimestamp()); + } + } + + @Subscribe + public void receiveGpsDOP(GpsDOPEvent e) { + GpsDOP dop = e.mDOP; + long now = System.currentTimeMillis(); + + if (dop.hasHdop()) { + consider(new PropertyKeyEvent(Measurement.PropertyKey.GPS_HDOP, dop.getHdop(), now)); + } + + if (dop.hasVdop()) { + consider(new PropertyKeyEvent(Measurement.PropertyKey.GPS_VDOP, dop.getVdop(), now)); + } + + if (dop.hasPdop()) { + consider(new PropertyKeyEvent(Measurement.PropertyKey.GPS_PDOP, dop.getPdop(), now)); + } + } + +} diff --git a/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/MeasurementProvider.java b/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/MeasurementProvider.java new file mode 100644 index 000000000..a2585e1d0 --- /dev/null +++ b/org.envirocar.algorithm/src/main/java/org/envirocar/algorithm/MeasurementProvider.java @@ -0,0 +1,44 @@ +package org.envirocar.algorithm; + +import org.envirocar.core.entity.Measurement; +import org.envirocar.obd.events.PropertyKeyEvent; +import org.envirocar.obd.events.Timestamped; + +import rx.Observable; + +/** + * TODO JavaDoc + */ +public interface MeasurementProvider { + + Observable measurements(long samplingRate); + + void consider(PropertyKeyEvent pke); + + void newPosition(Position pos); + + class Position implements Timestamped { + + private final long timestamp; + private final double latitude; + private final double longitude; + + public Position(long timestamp, double latitude, double longitude) { + this.timestamp = timestamp; + this.latitude = latitude; + this.longitude = longitude; + } + + public long getTimestamp() { + return timestamp; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + } +} diff --git a/org.envirocar.algorithm/src/main/res/values/strings.xml b/org.envirocar.algorithm/src/main/res/values/strings.xml new file mode 100644 index 000000000..cc45e6f81 --- /dev/null +++ b/org.envirocar.algorithm/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + algorithm + diff --git a/org.envirocar.algorithm/src/test/java/org/envirocar/algorithm/InterpolationMeasurementProviderTest.java b/org.envirocar.algorithm/src/test/java/org/envirocar/algorithm/InterpolationMeasurementProviderTest.java new file mode 100644 index 000000000..fecba66d1 --- /dev/null +++ b/org.envirocar.algorithm/src/test/java/org/envirocar/algorithm/InterpolationMeasurementProviderTest.java @@ -0,0 +1,85 @@ +package org.envirocar.algorithm; + +import org.envirocar.core.entity.Measurement; +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.commands.response.entity.LambdaProbeVoltageResponse; +import org.envirocar.obd.events.PropertyKeyEvent; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Test; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +public class InterpolationMeasurementProviderTest { + + @Test + public void testInterpolateTwo() { + InterpolationMeasurementProvider imp = new InterpolationMeasurementProvider(null); + + PropertyKeyEvent s1 = new PropertyKeyEvent(Measurement.PropertyKey.SPEED, 52, 1000); + PropertyKeyEvent s2 = new PropertyKeyEvent(Measurement.PropertyKey.SPEED, 95, 4000); + + //the temporal center, should be the average + double result = imp.interpolateTwo(s1.getValue(), s2.getValue(), 2500, s1.getTimestamp(), s2.getTimestamp()); + + Assert.assertThat(result, CoreMatchers.is(73.5)); + + //more at the and of the window + result = imp.interpolateTwo(s1.getValue(), s2.getValue(), 3000, s1.getTimestamp(), s2.getTimestamp()); + + BigDecimal bd = new BigDecimal(result); + bd = bd.setScale(2, RoundingMode.HALF_UP); + + Assert.assertThat(bd.doubleValue(), CoreMatchers.is(80.67)); + } + + @Test + public void testInterpolation() { + InterpolationMeasurementProvider imp = new InterpolationMeasurementProvider(null); + + PropertyKeyEvent m1 = new PropertyKeyEvent(Measurement.PropertyKey.MAF, 16.0, 1000); + PropertyKeyEvent m2 = new PropertyKeyEvent(Measurement.PropertyKey.MAF, 48.0, 3500); // this should be the result + PropertyKeyEvent m3 = new PropertyKeyEvent(Measurement.PropertyKey.MAF, 32.0, 5000); + + //the result should be 68.125 + PropertyKeyEvent s1 = new PropertyKeyEvent(Measurement.PropertyKey.SPEED, 52, 2000); + PropertyKeyEvent s2 = new PropertyKeyEvent(Measurement.PropertyKey.SPEED, 95, 6000); + + imp.consider(s1); + imp.consider(s2); + imp.consider(m1); + imp.consider(m2); + imp.consider(m3); + + imp.newPosition(new MeasurementProvider.Position(1000, 52.0, 7.0)); + imp.newPosition(new MeasurementProvider.Position(3500, 52.5, 7.25)); //this should be the result + + TestSubscriber ts = new TestSubscriber(); + + imp.measurements(500) + .subscribeOn(Schedulers.immediate()) + .observeOn(Schedulers.immediate()) + .first() + .subscribe(ts); + + List events = ts.getOnNextEvents(); + Assert.assertThat(events.size(), CoreMatchers.is(1)); + + Measurement first = events.get(0); + + Assert.assertThat(first.getTime(), CoreMatchers.is(3500L)); + + Assert.assertThat(first.getProperty(Measurement.PropertyKey.MAF), CoreMatchers.is(48.0)); + Assert.assertThat(first.getProperty(Measurement.PropertyKey.SPEED), CoreMatchers.is(68.125)); + + Assert.assertThat(first.getLatitude(), CoreMatchers.is(52.5)); + Assert.assertThat(first.getLongitude(), CoreMatchers.is(7.25)); + } + +} diff --git a/org.envirocar.app/AndroidManifest.xml b/org.envirocar.app/AndroidManifest.xml index 3517c0e69..88528d057 100644 --- a/org.envirocar.app/AndroidManifest.xml +++ b/org.envirocar.app/AndroidManifest.xml @@ -3,8 +3,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="org.envirocar.app" android:installLocation="internalOnly" - android:versionCode="27" - android:versionName="0.20.2"> + android:versionCode="28" + android:versionName="0.21.0-SNAPSHOT"> +>>>>>> feature/finetune-observables + android:versionName="0.21.0"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + w + + + + + + diff --git a/org.envirocar.app/build.gradle b/org.envirocar.app/build.gradle index 916dfbbe8..63b518305 100644 --- a/org.envirocar.app/build.gradle +++ b/org.envirocar.app/build.gradle @@ -21,24 +21,16 @@ dependencies { compile rootProject.ext.fabProgressCircle compile rootProject.ext.gson //compile rootProject.ext.guava - compile('com.github.afollestad.material-dialogs:core:0.8.5.0@aar') { transitive = true } - compile rootProject.ext.rxAndroid compile rootProject.ext.rxJava compile rootProject.ext.rxPreferences + compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.3.0' compile rootProject.ext.findBugs - - // Crouton - compile('de.keyboardsurfer.android.widget:crouton:1.8.5@aar') { - // exclusion is not necessary, but generally a good idea. - exclude group: 'com.google.android', module: 'support-v4' - } // Commons compress compile 'org.apache.commons:commons-compress:1.5' - // MapBox SDK compile(rootProject.ext.mapbox) { transitive = true @@ -64,6 +56,7 @@ dependencies { compile project(':org.envirocar.remote') compile project(':org.envirocar.obd') compile project(':org.envirocar.storage') + compile project(':org.envirocar.algorithm') } @@ -72,6 +65,13 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/org.envirocar.app/proguard-project.txt b/org.envirocar.app/proguard-project.txt index f2fe1559a..9abc02e28 100644 --- a/org.envirocar.app/proguard-project.txt +++ b/org.envirocar.app/proguard-project.txt @@ -18,3 +18,6 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} + +-keep class android.support.design.** { *; } +-keep interface android.support.design.** { *; } diff --git a/org.envirocar.app/res/anim/translate_slide_in_bottom_fragment.xml b/org.envirocar.app/res/anim/translate_slide_in_bottom_fragment.xml new file mode 100644 index 000000000..69e264d9b --- /dev/null +++ b/org.envirocar.app/res/anim/translate_slide_in_bottom_fragment.xml @@ -0,0 +1,7 @@ + + + + diff --git a/org.envirocar.app/res/anim/translate_slide_out_bottom.xml b/org.envirocar.app/res/anim/translate_slide_out_bottom.xml new file mode 100644 index 000000000..fded52947 --- /dev/null +++ b/org.envirocar.app/res/anim/translate_slide_out_bottom.xml @@ -0,0 +1,7 @@ + + + + diff --git a/org.envirocar.app/res/drawable-hdpi/ic_cloud_upload_white_24dp.png b/org.envirocar.app/res/drawable-hdpi/ic_cloud_upload_white_24dp.png new file mode 100644 index 000000000..5e0b464cf Binary files /dev/null and b/org.envirocar.app/res/drawable-hdpi/ic_cloud_upload_white_24dp.png differ diff --git a/org.envirocar.app/res/drawable-hdpi/tab_selected_cario.9.png b/org.envirocar.app/res/drawable-hdpi/tab_selected_cario.9.png deleted file mode 100644 index bbaec6c1a..000000000 Binary files a/org.envirocar.app/res/drawable-hdpi/tab_selected_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-hdpi/tab_selected_focused_cario.9.png b/org.envirocar.app/res/drawable-hdpi/tab_selected_focused_cario.9.png deleted file mode 100644 index 7b3c71794..000000000 Binary files a/org.envirocar.app/res/drawable-hdpi/tab_selected_focused_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-hdpi/tab_selected_pressed_cario.9.png b/org.envirocar.app/res/drawable-hdpi/tab_selected_pressed_cario.9.png deleted file mode 100644 index fb5afc675..000000000 Binary files a/org.envirocar.app/res/drawable-hdpi/tab_selected_pressed_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-hdpi/tab_unselected_focused_cario.9.png b/org.envirocar.app/res/drawable-hdpi/tab_unselected_focused_cario.9.png deleted file mode 100644 index cb774cb9f..000000000 Binary files a/org.envirocar.app/res/drawable-hdpi/tab_unselected_focused_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-hdpi/tab_unselected_pressed_cario.9.png b/org.envirocar.app/res/drawable-hdpi/tab_unselected_pressed_cario.9.png deleted file mode 100644 index 5de7011d3..000000000 Binary files a/org.envirocar.app/res/drawable-hdpi/tab_unselected_pressed_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-mdpi/ic_cloud_upload_white_24dp.png b/org.envirocar.app/res/drawable-mdpi/ic_cloud_upload_white_24dp.png new file mode 100644 index 000000000..aa640629a Binary files /dev/null and b/org.envirocar.app/res/drawable-mdpi/ic_cloud_upload_white_24dp.png differ diff --git a/org.envirocar.app/res/drawable-mdpi/tab_selected_cario.9.png b/org.envirocar.app/res/drawable-mdpi/tab_selected_cario.9.png deleted file mode 100644 index 9027cb49b..000000000 Binary files a/org.envirocar.app/res/drawable-mdpi/tab_selected_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-mdpi/tab_selected_focused_cario.9.png b/org.envirocar.app/res/drawable-mdpi/tab_selected_focused_cario.9.png deleted file mode 100644 index df5e4528a..000000000 Binary files a/org.envirocar.app/res/drawable-mdpi/tab_selected_focused_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-mdpi/tab_selected_pressed_cario.9.png b/org.envirocar.app/res/drawable-mdpi/tab_selected_pressed_cario.9.png deleted file mode 100644 index 451433e88..000000000 Binary files a/org.envirocar.app/res/drawable-mdpi/tab_selected_pressed_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-mdpi/tab_unselected_focused_cario.9.png b/org.envirocar.app/res/drawable-mdpi/tab_unselected_focused_cario.9.png deleted file mode 100644 index 56a62bd3c..000000000 Binary files a/org.envirocar.app/res/drawable-mdpi/tab_unselected_focused_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-mdpi/tab_unselected_pressed_cario.9.png b/org.envirocar.app/res/drawable-mdpi/tab_unselected_pressed_cario.9.png deleted file mode 100644 index 8359fd6dc..000000000 Binary files a/org.envirocar.app/res/drawable-mdpi/tab_unselected_pressed_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-xhdpi/ic_cloud_upload_white_24dp.png b/org.envirocar.app/res/drawable-xhdpi/ic_cloud_upload_white_24dp.png new file mode 100644 index 000000000..a9602d11b Binary files /dev/null and b/org.envirocar.app/res/drawable-xhdpi/ic_cloud_upload_white_24dp.png differ diff --git a/org.envirocar.app/res/drawable-xhdpi/tab_selected_cario.9.png b/org.envirocar.app/res/drawable-xhdpi/tab_selected_cario.9.png deleted file mode 100644 index 22d55dc1a..000000000 Binary files a/org.envirocar.app/res/drawable-xhdpi/tab_selected_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-xhdpi/tab_selected_focused_cario.9.png b/org.envirocar.app/res/drawable-xhdpi/tab_selected_focused_cario.9.png deleted file mode 100644 index 85435efe9..000000000 Binary files a/org.envirocar.app/res/drawable-xhdpi/tab_selected_focused_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-xhdpi/tab_selected_pressed_cario.9.png b/org.envirocar.app/res/drawable-xhdpi/tab_selected_pressed_cario.9.png deleted file mode 100644 index a70130434..000000000 Binary files a/org.envirocar.app/res/drawable-xhdpi/tab_selected_pressed_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-xhdpi/tab_unselected_focused_cario.9.png b/org.envirocar.app/res/drawable-xhdpi/tab_unselected_focused_cario.9.png deleted file mode 100644 index c2d1fff54..000000000 Binary files a/org.envirocar.app/res/drawable-xhdpi/tab_unselected_focused_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-xhdpi/tab_unselected_pressed_cario.9.png b/org.envirocar.app/res/drawable-xhdpi/tab_unselected_pressed_cario.9.png deleted file mode 100644 index 7379bddae..000000000 Binary files a/org.envirocar.app/res/drawable-xhdpi/tab_unselected_pressed_cario.9.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable-xxhdpi/ic_cloud_upload_white_24dp.png b/org.envirocar.app/res/drawable-xxhdpi/ic_cloud_upload_white_24dp.png new file mode 100644 index 000000000..3ff57ad3e Binary files /dev/null and b/org.envirocar.app/res/drawable-xxhdpi/ic_cloud_upload_white_24dp.png differ diff --git a/org.envirocar.app/res/drawable-xxxhdpi/ic_cloud_upload_white_24dp.png b/org.envirocar.app/res/drawable-xxxhdpi/ic_cloud_upload_white_24dp.png new file mode 100644 index 000000000..2180f73e8 Binary files /dev/null and b/org.envirocar.app/res/drawable-xxxhdpi/ic_cloud_upload_white_24dp.png differ diff --git a/org.envirocar.app/res/drawable/envirocar_background.xml b/org.envirocar.app/res/drawable/envirocar_background.xml deleted file mode 100644 index c9135abf9..000000000 --- a/org.envirocar.app/res/drawable/envirocar_background.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/org.envirocar.app/res/drawable/img_envirocar_logo.png b/org.envirocar.app/res/drawable/img_envirocar_logo.png new file mode 100644 index 000000000..318d61da2 Binary files /dev/null and b/org.envirocar.app/res/drawable/img_envirocar_logo.png differ diff --git a/org.envirocar.app/res/drawable/img_envirocar_logo_white.png b/org.envirocar.app/res/drawable/img_envirocar_logo_white.png new file mode 100644 index 000000000..7031cf095 Binary files /dev/null and b/org.envirocar.app/res/drawable/img_envirocar_logo_white.png differ diff --git a/org.envirocar.app/res/drawable/tab_indicator_ab_cario.xml b/org.envirocar.app/res/drawable/tab_indicator_ab_cario.xml deleted file mode 100644 index 10a85764a..000000000 --- a/org.envirocar.app/res/drawable/tab_indicator_ab_cario.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/org.envirocar.app/res/drawable/unbenannt.png b/org.envirocar.app/res/drawable/unbenannt.png deleted file mode 100644 index 8251e2bc7..000000000 Binary files a/org.envirocar.app/res/drawable/unbenannt.png and /dev/null differ diff --git a/org.envirocar.app/res/drawable/upload_button.jpg b/org.envirocar.app/res/drawable/upload_button.jpg deleted file mode 100644 index 796f0d4ea..000000000 Binary files a/org.envirocar.app/res/drawable/upload_button.jpg and /dev/null differ diff --git a/org.envirocar.app/res/layout/activity_car_selection_layout.xml b/org.envirocar.app/res/layout/activity_car_selection_layout.xml index 32730fba9..8b5725464 100644 --- a/org.envirocar.app/res/layout/activity_car_selection_layout.xml +++ b/org.envirocar.app/res/layout/activity_car_selection_layout.xml @@ -19,99 +19,127 @@ with the enviroCar app. If not, see http://www.gnu.org/licenses/. --> - + android:layout_height="match_parent"> - - - - + android:background="@color/white"> - + android:layout_height="1dp" + android:elevation="6dp" + app:elevation="6dp"/> - + - + android:orientation="horizontal"> + + + + + + + + + + + + + + + + + + + + - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:gravity="center" + android:text="@string/car_selection_no_car_selected"/> - + - + - - - - - + android:background="#3f3f3f3f" + android:visibility="gone"/> - - + - + + - - - + - + diff --git a/org.envirocar.app/res/layout/activity_car_selection_newcar_fragment.xml b/org.envirocar.app/res/layout/activity_car_selection_newcar_fragment.xml new file mode 100644 index 000000000..7a6933383 --- /dev/null +++ b/org.envirocar.app/res/layout/activity_car_selection_newcar_fragment.xml @@ -0,0 +1,360 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.envirocar.app/res/layout/activity_car_selection_newcar_spinner_item.xml b/org.envirocar.app/res/layout/activity_car_selection_newcar_spinner_item.xml new file mode 100644 index 000000000..14cb25373 --- /dev/null +++ b/org.envirocar.app/res/layout/activity_car_selection_newcar_spinner_item.xml @@ -0,0 +1,12 @@ + + + + diff --git a/org.envirocar.app/res/layout/activity_help_layout_general.xml b/org.envirocar.app/res/layout/activity_help_layout_general.xml index 8b4272297..7c0e075a4 100644 --- a/org.envirocar.app/res/layout/activity_help_layout_general.xml +++ b/org.envirocar.app/res/layout/activity_help_layout_general.xml @@ -41,9 +41,19 @@ app:contentScrim="?attr/colorPrimary" app:expandedTitleMarginEnd="64dp" app:expandedTitleMarginStart="48dp" - app:layout_scrollFlags="scroll|exitUntilCollapsed"> + app:layout_scrollFlags="scroll|exitUntilCollapsed" + app:statusBarScrim="@android:color/transparent"> - + + - \ No newline at end of file + diff --git a/org.envirocar.app/res/layout/activity_login.xml b/org.envirocar.app/res/layout/activity_login.xml index d4c8de88f..4276c711a 100644 --- a/org.envirocar.app/res/layout/activity_login.xml +++ b/org.envirocar.app/res/layout/activity_login.xml @@ -78,9 +78,9 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" + android:layout_marginBottom="@dimen/spacing_medium" android:layout_marginLeft="@dimen/spacing_small" android:layout_marginRight="@dimen/spacing_small" - android:layout_marginBottom="@dimen/spacing_medium" android:orientation="horizontal"> + android:orientation="vertical" + android:visibility="gone"> + + + + + + + + @@ -228,4 +249,4 @@ - \ No newline at end of file + diff --git a/org.envirocar.app/res/layout/activity_track_details_attributes.xml b/org.envirocar.app/res/layout/activity_track_details_attributes.xml index 0f9971d46..73f3b0555 100644 --- a/org.envirocar.app/res/layout/activity_track_details_attributes.xml +++ b/org.envirocar.app/res/layout/activity_track_details_attributes.xml @@ -29,7 +29,8 @@ @@ -61,14 +62,15 @@ android:layout_centerVertical="true" android:layout_toRightOf="@id/activity_track_details_attr_car_text" android:gravity="right|center_vertical" - android:text="car" + android:text="Mercedes Benz A 170" android:textColor="#939393"/> @@ -102,7 +104,8 @@ @@ -136,7 +139,8 @@ @@ -165,6 +169,7 @@ android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" + android:gravity="right" android:text="Consumption" android:textColor="#939393"/> @@ -172,7 +177,8 @@ @@ -201,9 +207,17 @@ android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" + android:gravity="right" android:text="emission" android:textColor="#939393"/> + - \ No newline at end of file + diff --git a/org.envirocar.app/res/layout/fragment_tracklist.xml b/org.envirocar.app/res/layout/fragment_tracklist.xml index bf7c4f7a5..650882f7a 100644 --- a/org.envirocar.app/res/layout/fragment_tracklist.xml +++ b/org.envirocar.app/res/layout/fragment_tracklist.xml @@ -79,4 +79,15 @@ - \ No newline at end of file + + + + + diff --git a/org.envirocar.app/res/layout/nav_drawer_list_header.xml b/org.envirocar.app/res/layout/nav_drawer_list_header.xml index 662c3faa7..29121ae15 100644 --- a/org.envirocar.app/res/layout/nav_drawer_list_header.xml +++ b/org.envirocar.app/res/layout/nav_drawer_list_header.xml @@ -92,4 +92,4 @@ - \ No newline at end of file + diff --git a/org.envirocar.app/res/drawable/tabs_background_cario_gradient.xml b/org.envirocar.app/res/menu/menu_car_selection_add_car.xml similarity index 71% rename from org.envirocar.app/res/drawable/tabs_background_cario_gradient.xml rename to org.envirocar.app/res/menu/menu_car_selection_add_car.xml index f69efdb94..658c2495a 100644 --- a/org.envirocar.app/res/drawable/tabs_background_cario_gradient.xml +++ b/org.envirocar.app/res/menu/menu_car_selection_add_car.xml @@ -1,32 +1,29 @@ - - - - - - - - \ No newline at end of file + + + + + diff --git a/org.envirocar.app/res/menu/menu_logbook_add_fueling.xml b/org.envirocar.app/res/menu/menu_logbook_add_fueling.xml index 52bad7d1f..e11915163 100644 --- a/org.envirocar.app/res/menu/menu_logbook_add_fueling.xml +++ b/org.envirocar.app/res/menu/menu_logbook_add_fueling.xml @@ -23,7 +23,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> - \ No newline at end of file + app:showAsAction="always"/> + diff --git a/org.envirocar.app/res/menu/menu_nav_drawer.xml b/org.envirocar.app/res/menu/menu_nav_drawer.xml index 13d445143..ee8f5ae6d 100644 --- a/org.envirocar.app/res/menu/menu_nav_drawer.xml +++ b/org.envirocar.app/res/menu/menu_nav_drawer.xml @@ -20,54 +20,53 @@ --> - + + android:title="@string/menu_nav_drawer_dashboard"/> + android:title="@string/menu_nav_drawer_my_tracks"/> + android:title="@string/menu_nav_drawer_logbook"/> + android:title="@string/menu_nav_drawer_login"/> + android:title="@string/menu_nav_drawer_close_ec"/> + android:title="@string/menu_nav_drawer_settings"/> + android:title="@string/menu_nav_drawer_help"/> + android:title="@string/menu_nav_drawer_send_log"/> - \ No newline at end of file + diff --git a/org.envirocar.app/res/values-de/strings.xml b/org.envirocar.app/res/values-de/strings.xml index f5fbae1ff..b8abf6507 100644 --- a/org.envirocar.app/res/values-de/strings.xml +++ b/org.envirocar.app/res/values-de/strings.xml @@ -148,7 +148,8 @@ Registrieren Dashboard - Meine Fahrten + Statistiken + Distanz Willkommen, %s Auf Wiederstehen, %s @@ -213,12 +214,6 @@ Server Fehler - Bitte versuchen Sie es später erneut - Nutzungsbedingungen akzeptieren? - Bevor Sie fortfahren, müssen Sie unsere Nutzungsbedingungen akzeptieren - Entschuldigen Sie die erneute Störung. Wir haben unsere Nutzungsbedingungen geändert - Sie müssen die Nutzungsbedingungen akzeptieren, bevor Sie Tracks hochladen können - Speichere Nutzungsbedingungs-Status auf Server - Bitte beachten Sie, dass die Berechnung von Verbrauch/CO2 für Diesel-Fahrzeuge nocht nicht unterstützt wird Ein hochzuladende Track enthielt keine Messungen nach Verbergen der Start- und Endpunkte. Er wurde nicht hochgeladen, ist aber weiterhin lokal verfügbar. Ein hochzuladende Track enthielt keine Messungen. diff --git a/org.envirocar.app/res/values-de/strings_activity_settings.xml b/org.envirocar.app/res/values-de/strings_activity_settings.xml index 9a9e6edd2..5d57d485e 100644 --- a/org.envirocar.app/res/values-de/strings_activity_settings.xml +++ b/org.envirocar.app/res/values-de/strings_activity_settings.xml @@ -75,4 +75,6 @@ Das Zeit-Delta zwischen zwei MEssungen in Sekunden (aktueller Wert: %s). HINWEIS: Je niedriger der Wert, desto größer ist die Datenmenge. Ändern Sie diesen Wert nur dann, wenn Sie such auch der Konsequenzen bewusst sind. Debug-Protokollierung aktivieren Erhöhung der Protokollierungsstufe (in der Ausgabe/Problembericht verwendet). + Diesel Verbrauchsberechnung aktivieren + Aktiviert die Verbrauchsschätzung für Dieselfahrzeuge.\n\nBeachten Sie, dass es sich hierbei um ein nicht getestetes Feature im Beta-Stadium handelt. Daher ist unbedingt zur Enables the estimation of consumption values for diesel. NOTE: This feature is just a beta feature. diff --git a/org.envirocar.app/res/values-de/strings_car_selection.xml b/org.envirocar.app/res/values-de/strings_car_selection.xml index 89c78d721..201a79087 100644 --- a/org.envirocar.app/res/values-de/strings_car_selection.xml +++ b/org.envirocar.app/res/values-de/strings_car_selection.xml @@ -23,4 +23,15 @@ Meine Fahrzeuge Kein Fahrzeugtyp ausgewählt Fahrzeug Hinzufügen + Geben Sie die Details ihres Fahrzeug ein... + + %s %s ausgewählt. + %s %s gelöscht. + %s %s wurde hinzugefügt. + %s %s ist bereits in der Liste. + + Hersteller + Modell + Herstellungsjahr + Hubraum (cc) diff --git a/org.envirocar.app/res/values-de/strings_help.xml b/org.envirocar.app/res/values-de/strings_help.xml index 83c777b05..7a95de05e 100644 --- a/org.envirocar.app/res/values-de/strings_help.xml +++ b/org.envirocar.app/res/values-de/strings_help.xml @@ -55,7 +55,7 @@ 4.3. Herunterladen von hochgeladenen Fahrten - 5. Probleme and Feedback + 5. Probleme und Feedback Bei festgestellten Problemen möchten wir Sie bitten, uns ein Fehlerprotokoll zu senden (envirocar@52north.org). Das Fehlerprotokoll enthält Informationen über Prozessverläufe im System. Es unterstützt uns dabei, die Ursachen von Fehlfunktionen zu erkennen und zu beheben. diff --git a/org.envirocar.app/res/values-de/strings_menu.xml b/org.envirocar.app/res/values-de/strings_menu.xml new file mode 100644 index 000000000..18824f58f --- /dev/null +++ b/org.envirocar.app/res/values-de/strings_menu.xml @@ -0,0 +1,14 @@ + + + Dashboard + Meine Fahrten + Einloggen/Registrieren + Logbuch + + Beenden + + Eigenschaften + Hilfe + Problem Melden + + diff --git a/org.envirocar.app/res/values-de/strings_terms_of_use.xml b/org.envirocar.app/res/values-de/strings_terms_of_use.xml new file mode 100644 index 000000000..f75cc34e8 --- /dev/null +++ b/org.envirocar.app/res/values-de/strings_terms_of_use.xml @@ -0,0 +1,10 @@ + + + Nutzungsbedingungen akzeptieren? + Akzeptieren + Ablehnen + Bevor Sie fortfahren, müssen Sie unsere Nutzungsbedingungen akzeptieren + Entschuldigen Sie die erneute Störung. Wir haben unsere Nutzungsbedingungen geändert + Sie müssen die Nutzungsbedingungen akzeptieren, bevor Sie Tracks hochladen können + Speichere Nutzungsbedingungs-Status auf Server + diff --git a/org.envirocar.app/res/values-de/strings_track_list.xml b/org.envirocar.app/res/values-de/strings_track_list.xml index e02bdb732..8d7fd4d7c 100644 --- a/org.envirocar.app/res/values-de/strings_track_list.xml +++ b/org.envirocar.app/res/values-de/strings_track_list.xml @@ -42,6 +42,8 @@ Fehler beim Laden der Fahrten.\nBitte versuchen Sie es erneut. KEINE LOKALE FAHRTEN Sie verfügen derzeitig über keine lokale Fahrten. + KEINE HOCHGELADENEN FAHRTEN + Sie verfügen derzeitig über keine hochgeladenen Fahrten. NICHT ANGEMELDET Um Zugriff auf Ihre hochgeladenen Fahrten zu erhalten,\nloggen Sie sich bitte zuerst ein. Keine Verbdinung zum Internet. @@ -52,16 +54,17 @@ Keine Verbindung zum Server. Detailansicht - Lösche Fahrt - Fahrt als Open Data hochladen - Exportiere Track + Löschen + Als Open Data hochladen + Exportieren Dauer Distanz Auto: Beginn: Ende: - Verbrauch: - CO\u2082 Ausstoß: + Verbrauch*: + CO\u2082 Ausstoß*: + DIESEL NICHT\nUNTERSTÜTZT diff --git a/org.envirocar.app/res/values/strings.xml b/org.envirocar.app/res/values/strings.xml index 48c21c4ab..80964b612 100644 --- a/org.envirocar.app/res/values/strings.xml +++ b/org.envirocar.app/res/values/strings.xml @@ -26,10 +26,11 @@ enviroCar - Settings - Help + Track Statistics + Distance + About - Report Issue + My Garage Checklist @@ -97,7 +98,7 @@ Stop Upload Delete - Sign in/Register + Single Track Automatic Track Creation (experimental) Do you want to record a single track or start automatic creation? @@ -156,8 +157,8 @@ Sign out Register - Dashboard - My Tracks + + Car Welcome, %s Goodbye, %s @@ -205,12 +206,6 @@ A track to be uploaded did not contain measurements. Server Error - Please try again later - Accept the Terms Of Use? - Before you proceed, you have to accept our Terms Of Use - Sorry to bother you again. We have changed our Terms Of Use - Before you upload any Track, you have to accept the Terms Of Use - Updating Terms Of Use State on Server - Please note that consumption/CO2 calculation for Diesel cars is not yet supported 0 kg/h @@ -228,7 +223,7 @@ Do not show this message again? Ok - Logbook + diff --git a/org.envirocar.app/res/values/strings_activity_settings.xml b/org.envirocar.app/res/values/strings_activity_settings.xml index d13ddcc4f..7a807c853 100644 --- a/org.envirocar.app/res/values/strings_activity_settings.xml +++ b/org.envirocar.app/res/values/strings_activity_settings.xml @@ -72,4 +72,6 @@ Only consider changing if you are aware of the consequences. Enable Debug Logging Increase the log level (used in issue/problem reports) + Enable Diesel Consumption + Enables the estimation of consumption values for diesel. NOTE: This feature is just a beta feature. diff --git a/org.envirocar.app/res/values/strings_car_selection.xml b/org.envirocar.app/res/values/strings_car_selection.xml index 3fd170fcb..53a25e2c9 100644 --- a/org.envirocar.app/res/values/strings_car_selection.xml +++ b/org.envirocar.app/res/values/strings_car_selection.xml @@ -22,5 +22,19 @@ My Cars No car type selected. - Add Car + Create Car + Enter the details of your car... + + %s %s has been selected as my car. + %s %s has been deleted. + %s %s has been added to the list. + %s %s is already in the list. + + Please be as specific as possible with your details. This information is used for internal calculation of essential attributes. False information will ultimately lead to wrong calculated values. + + Manufacturer + Model + Construction Year + Engine Displacement (cc) + diff --git a/org.envirocar.app/res/values/strings_menu.xml b/org.envirocar.app/res/values/strings_menu.xml new file mode 100644 index 000000000..8275abae6 --- /dev/null +++ b/org.envirocar.app/res/values/strings_menu.xml @@ -0,0 +1,14 @@ + + + Dashboard + My Tracks + Sign in/Register + Logbook + + Close enviroCar + + Settings + Help + Report Issue + + diff --git a/org.envirocar.app/res/values/strings_notification.xml b/org.envirocar.app/res/values/strings_notification.xml index 71d165c30..ca8482cef 100644 --- a/org.envirocar.app/res/values/strings_notification.xml +++ b/org.envirocar.app/res/values/strings_notification.xml @@ -55,7 +55,6 @@ Stopping the track and storing the data. - Establishing connection to OBDII adapter Connected to OBDII adapter. Recording. diff --git a/org.envirocar.app/res/values/strings_terms_of_use.xml b/org.envirocar.app/res/values/strings_terms_of_use.xml new file mode 100644 index 000000000..1583990c8 --- /dev/null +++ b/org.envirocar.app/res/values/strings_terms_of_use.xml @@ -0,0 +1,10 @@ + + + Accept the Terms Of Use? + Accept + Reject + Before you proceed, you have to accept our Terms Of Use + Sorry to bother you again. We have changed our Terms Of Use + Before you upload any Track, you have to accept the Terms Of Use + Updating Terms Of Use State on Server + diff --git a/org.envirocar.app/res/values/strings_track_list.xml b/org.envirocar.app/res/values/strings_track_list.xml index 4ebb08ca4..1449b8a90 100644 --- a/org.envirocar.app/res/values/strings_track_list.xml +++ b/org.envirocar.app/res/values/strings_track_list.xml @@ -43,6 +43,8 @@ Error while retrieving tracks.\nPlease retry. NO LOCAL TRACKS You have 0 local tracks + NO UPLOADED TRACKS + You have 0 uploaded tracks NOT LOGGED IN To access your remote tracks\nyou have to log in fist. NOT CONNECTED @@ -63,6 +65,11 @@ End: Consumption: CO\u2082 Emission: + NOT SUPPORTED\nFOR DIESEL + Upload All Local Tracks? + You are about to upload all tracks. This means that all tracks will be uploaded and assigned to your account.\n\nDo you want to continue? + Uploading Tracks... + Uploading Track %s of %s diff --git a/org.envirocar.app/res/values/styles.xml b/org.envirocar.app/res/values/styles.xml index b17de2fd3..cee477308 100644 --- a/org.envirocar.app/res/values/styles.xml +++ b/org.envirocar.app/res/values/styles.xml @@ -109,4 +109,14 @@ @color/white_cario @color/white_cario - \ No newline at end of file + + + diff --git a/org.envirocar.app/res/values/styles_cario.xml b/org.envirocar.app/res/values/styles_cario.xml index 938d95e6e..13a92b65b 100644 --- a/org.envirocar.app/res/values/styles_cario.xml +++ b/org.envirocar.app/res/values/styles_cario.xml @@ -25,7 +25,6 @@ @drawable/selectable_background_cario @style/PopupMenu.Cario @style/DropDownListView.Cario - @style/ActionBarTabStyle.Cario @style/DropDownNav.Cario @drawable/cab_background_top_cario @drawable/cab_background_bottom_cario @@ -66,9 +65,6 @@ @drawable/selectable_background_cario - - - \ No newline at end of file + diff --git a/org.envirocar.app/res/xml/preferences_optional.xml b/org.envirocar.app/res/xml/preferences_optional.xml index 0e5fc3f49..12257d814 100644 --- a/org.envirocar.app/res/xml/preferences_optional.xml +++ b/org.envirocar.app/res/xml/preferences_optional.xml @@ -32,14 +32,17 @@ android:title="@string/sampling_rate_title" enviroCar:max="15" enviroCar:min="0" /> - + + + + diff --git a/org.envirocar.app/src/org/envirocar/app/BaseApplication.java b/org.envirocar.app/src/org/envirocar/app/BaseApplication.java index 6a3dc163b..55c2ff6d2 100644 --- a/org.envirocar.app/src/org/envirocar/app/BaseApplication.java +++ b/org.envirocar.app/src/org/envirocar/app/BaseApplication.java @@ -33,7 +33,6 @@ import org.acra.ACRA; import org.acra.annotation.ReportsCrashes; import org.envirocar.app.handler.PreferenceConstants; -import org.envirocar.app.injection.InjectionApplicationModule; import org.envirocar.core.injection.InjectionModuleProvider; import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.ACRACustomSender; @@ -129,6 +128,9 @@ public void onReceive(Context context, Intent intent) { boolean obfus = prefs.getBoolean(PreferenceConstants.OBFUSCATE_POSITION, false); LOGGER.info("Obfuscation enabled? "+ obfus); + + Logger.initialize(Util.getVersionString(this), + prefs.getBoolean(PreferenceConstants.ENABLE_DEBUG_LOGGING, false)); } @Override @@ -165,7 +167,7 @@ public ObjectGraph getObjectGraph() { @Override public List getInjectionModules() { return Arrays.asList( - new InjectionApplicationModule(this)); + new BaseApplicationModule(this)); } @Override diff --git a/org.envirocar.app/src/org/envirocar/app/injection/InjectionApplicationModule.java b/org.envirocar.app/src/org/envirocar/app/BaseApplicationModule.java similarity index 51% rename from org.envirocar.app/src/org/envirocar/app/injection/InjectionApplicationModule.java rename to org.envirocar.app/src/org/envirocar/app/BaseApplicationModule.java index 016b8e13f..d64fd493a 100644 --- a/org.envirocar.app/src/org/envirocar/app/injection/InjectionApplicationModule.java +++ b/org.envirocar.app/src/org/envirocar/app/BaseApplicationModule.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.app.injection; +package org.envirocar.app; import android.app.Application; import android.content.Context; @@ -27,58 +27,33 @@ import com.squareup.otto.Bus; import com.squareup.otto.ThreadEnforcer; -import org.envirocar.app.CommandListener; -import org.envirocar.app.TrackHandler; import org.envirocar.app.events.TrackDetailsProvider; -import org.envirocar.app.handler.BluetoothHandler; -import org.envirocar.app.handler.CarPreferenceHandler; -import org.envirocar.app.handler.LocationHandler; +import org.envirocar.app.handler.HandlerModule; import org.envirocar.app.handler.TemporaryFileManager; -import org.envirocar.app.handler.TermsOfUseManager; -import org.envirocar.app.handler.UploadManager; -import org.envirocar.app.handler.UserHandler; +import org.envirocar.app.handler.TrackRecordingHandler; import org.envirocar.app.services.NotificationHandler; -import org.envirocar.app.services.OBDConnectionService; import org.envirocar.app.services.SystemStartupService; import org.envirocar.app.services.TrackUploadService; -import org.envirocar.app.storage.DbAdapter; -import org.envirocar.app.storage.DbAdapterImpl; -import org.envirocar.app.storage.LazyLoadingStrategy; -import org.envirocar.app.storage.LazyLoadingStrategyImpl; -import org.envirocar.app.view.LogbookFragment; import org.envirocar.app.view.LoginActivity; -import org.envirocar.app.view.RegisterFragment; import org.envirocar.app.view.carselection.CarSelectionActivity; -import org.envirocar.app.view.dashboard.DashboardMapFragment; -import org.envirocar.app.view.dashboard.DashboardTempomatFragment; -import org.envirocar.app.view.dashboard.DashboardTrackDetailsFragment; -import org.envirocar.app.view.dashboard.DashboardTrackMapFragment; -import org.envirocar.app.view.dashboard.DashboardTrackSettingsFragment; +import org.envirocar.app.view.carselection.CarSelectionAddCarFragment; import org.envirocar.app.view.logbook.LogbookActivity; import org.envirocar.app.view.logbook.LogbookAddFuelingFragment; import org.envirocar.app.view.obdselection.OBDSelectionActivity; -import org.envirocar.app.view.obdselection.OBDSelectionFragment; import org.envirocar.app.view.preferences.BluetoothDiscoveryIntervalPreference; import org.envirocar.app.view.preferences.BluetoothPairingPreference; import org.envirocar.app.view.preferences.SelectBluetoothPreference; -import org.envirocar.app.view.settings.NewSettingsActivity; -import org.envirocar.app.view.settings.OBDSettingsFragment; +import org.envirocar.app.view.settings.SettingsActivity; import org.envirocar.app.view.trackdetails.TrackDetailsActivity; import org.envirocar.app.view.trackdetails.TrackStatisticsActivity; -import org.envirocar.app.view.tracklist.AbstractTrackListCardFragment; -import org.envirocar.app.view.tracklist.TrackListLocalCardFragment; -import org.envirocar.app.view.tracklist.TrackListPagerFragment; -import org.envirocar.app.view.tracklist.TrackListRemoteCardFragment; import org.envirocar.core.injection.InjectApplicationScope; import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; -import org.envirocar.obd.Collector; -import org.envirocar.obd.FeatureFlags; import org.envirocar.remote.CacheModule; import org.envirocar.remote.DAOProvider; import org.envirocar.remote.RemoteModule; import org.envirocar.remote.service.EnviroCarService; -import org.envirocar.storage.EnviroCarDBModule; +import org.envirocar.storage.DatabaseModule; import javax.inject.Singleton; @@ -95,56 +70,33 @@ includes = { RemoteModule.class, CacheModule.class, - EnviroCarDBModule.class + DatabaseModule.class, + HandlerModule.class }, injects = { - TermsOfUseManager.class, - CarPreferenceHandler.class, - LogbookFragment.class, - RegisterFragment.class, BluetoothPairingPreference.class, - BluetoothHandler.class, SelectBluetoothPreference.class, TemporaryFileManager.class, SystemStartupService.class, NotificationHandler.class, - CommandListener.class, - DbAdapterImpl.class, - LocationHandler.class, - OBDConnectionService.class, BluetoothDiscoveryIntervalPreference.class, - Collector.class, - LazyLoadingStrategyImpl.class, - TrackHandler.class, - UserHandler.class, TrackDetailsActivity.class, CarSelectionActivity.class, OBDSelectionActivity.class, - DashboardTrackDetailsFragment.class, - DashboardTempomatFragment.class, - DashboardTrackSettingsFragment.class, - DashboardMapFragment.class, - OBDSelectionFragment.class, - DashboardTrackMapFragment.class, TrackStatisticsActivity.class, LoginActivity.class, - NewSettingsActivity.class, - OBDSettingsFragment.class, - TrackListPagerFragment.class, - AbstractTrackListCardFragment.class, - TrackListLocalCardFragment.class, - TrackListRemoteCardFragment.class, + SettingsActivity.class, TrackUploadService.class, - UploadManager.class, LogbookActivity.class, - LogbookAddFuelingFragment.class + LogbookAddFuelingFragment.class, + CarSelectionAddCarFragment.class }, staticInjections = {EnviroCarService.class}, library = true, complete = false ) -public class InjectionApplicationModule { - private static final Logger LOGGER = Logger.getLogger(InjectionApplicationModule.class); +public class BaseApplicationModule { + private static final Logger LOGGER = Logger.getLogger(BaseApplicationModule.class); private final Application mApplication; private final Context mAppContext; @@ -156,7 +108,7 @@ public class InjectionApplicationModule { * * @param application the current application. */ - public InjectionApplicationModule(Application application) { + public BaseApplicationModule(Application application) { this.mApplication = application; this.mAppContext = application.getApplicationContext(); } @@ -216,32 +168,6 @@ DAOProvider provideDAOProvider() { return new DAOProvider(mAppContext); } - /** - * Provides the UserHandler of the application - * - * @return the UserHandler of the application - */ - @Provides - @Singleton - UserHandler provideUserManager() { - return new UserHandler(mAppContext); - } - - @Provides - @Singleton - org.envirocar.core.UserManager provideUserManagerImpl(UserHandler userHandler) { return userHandler; } - - /** - * Provides the FeatureFlags of the application - * - * @return the FeatureFlags of the application - */ - @Provides - @Singleton - FeatureFlags provideFeatureFlagsManager() { - return new FeatureFlags(mAppContext); - } - /** * Provides the TemporaryFileManager of the application * @@ -253,48 +179,6 @@ TemporaryFileManager provideTemporaryFileManager() { return new TemporaryFileManager(mAppContext); } - /** - * Provides the TemporaryFileManager of the application - * - * @return the TemporaryFileManager of the application. - */ - @Provides - @Singleton - DbAdapter provideDBAdapter() { - - DbAdapter adapter = null; - try { - adapter = new DbAdapterImpl(mAppContext); - } catch (InstantiationException e) { - LOGGER.warn("Could not initalize the database layer. The app will probably work " + - "unstable."); - LOGGER.warn(e.getMessage(), e); - } - - return adapter; - } - - /** - * Provides the TermsOfUseManager of the application - * - * @return the TermsOfUseManager of the application. - */ - @Provides - @Singleton - TermsOfUseManager provideTermsOfUseManager() { - return new TermsOfUseManager(mAppContext); - } - - /** - * Provides the CarManager of the application - * - * @return the CarManager of the application. - */ - @Provides - @Singleton - CarPreferenceHandler provideCarManager() { - return new CarPreferenceHandler(mAppContext); - } /** * Provides the CarManager of the application @@ -309,36 +193,8 @@ NotificationHandler provideNotificationHandler() { @Provides @Singleton - BluetoothHandler provideBluetoothHandler() { - return new BluetoothHandler(mAppContext); - } - - /** - * Provides the LocationHandler of the application. - * - * @return the LocationHandler of the application. - */ - @Provides - @Singleton - LocationHandler provideLocationHandler() { - return new LocationHandler(mAppContext); - } - - /** - * Provides the LazyLoadingStrategy of the application. - * - * @return the LazyLoadingStrategy of the application. - */ - @Provides - @Singleton - LazyLoadingStrategy provideLazyLoadingStrategy() { - return new LazyLoadingStrategyImpl(mAppContext); - } - - @Provides - @Singleton - TrackHandler provideTrackHandler() { - return new TrackHandler(mAppContext); + TrackRecordingHandler provideTrackHandler() { + return new TrackRecordingHandler(mAppContext); } @Provides diff --git a/org.envirocar.app/src/org/envirocar/app/BaseMainActivity.java b/org.envirocar.app/src/org/envirocar/app/BaseMainActivity.java index 3fb62ef47..036bd71d1 100644 --- a/org.envirocar.app/src/org/envirocar/app/BaseMainActivity.java +++ b/org.envirocar.app/src/org/envirocar/app/BaseMainActivity.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.support.design.widget.NavigationView; +import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.GravityCompat; @@ -35,6 +36,7 @@ import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.widget.Toolbar; import android.text.Html; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -51,7 +53,6 @@ import org.envirocar.app.handler.PreferencesHandler; import org.envirocar.app.handler.TemporaryFileManager; import org.envirocar.app.handler.UserHandler; -import org.envirocar.app.injection.InjectionActivityModule; import org.envirocar.app.services.OBDConnectionService; import org.envirocar.app.services.SystemStartupService; import org.envirocar.app.view.HelpActivity; @@ -60,7 +61,7 @@ import org.envirocar.app.view.TroubleshootingFragment; import org.envirocar.app.view.dashboard.DashboardMainFragment; import org.envirocar.app.view.logbook.LogbookActivity; -import org.envirocar.app.view.settings.NewSettingsActivity; +import org.envirocar.app.view.settings.SettingsActivity; import org.envirocar.app.view.tracklist.TrackListPagerFragment; import org.envirocar.core.entity.Announcement; import org.envirocar.core.entity.User; @@ -89,10 +90,7 @@ import butterknife.ButterKnife; import butterknife.InjectView; -import de.keyboardsurfer.android.widget.crouton.Crouton; -import de.keyboardsurfer.android.widget.crouton.Style; import rx.Scheduler; -import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.subscriptions.CompositeSubscription; @@ -103,12 +101,11 @@ * @author dewall */ public class BaseMainActivity extends BaseInjectorActivity { - private static final Logger LOGGER = Logger.getLogger(BaseApplication.class); + private static final Logger LOGGER = Logger.getLogger(BaseMainActivity.class); public static final int TRACK_MODE_SINGLE = 0; public static final int TRACK_MODE_AUTO = 1; - private static final String TRACK_MODE = "trackMode"; private static final String SEEN_ANNOUNCEMENTS = "seenAnnouncements"; private static final String TROUBLESHOOTING_TAG = "TROUBLESHOOTING"; @@ -150,7 +147,6 @@ public class BaseMainActivity extends BaseInjectorActivity { private boolean paused; private ActionBarDrawerToggle mDrawerToggle; - private Subscription mPreferenceSubscription; private BluetoothServiceState mServiceState = BluetoothServiceState.SERVICE_STOPPED; private Fragment mCurrentFragment; private Fragment mStartupFragment; @@ -162,15 +158,40 @@ public class BaseMainActivity extends BaseInjectorActivity { @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + /** + * try-catch: very dirty hack for broken fragmentmanager impl on some (one?) device + */ + boolean noInstantiatedExceptionReceived = false; + try { + super.onCreate(savedInstanceState); + } + catch (IllegalStateException e) { + LOGGER.warn("Trying to reconstruct fragment state. Got Exception", e); + if (e.getMessage().contains("No instantiated fragment for index #")) { + noInstantiatedExceptionReceived = true; + } + } // Set the content view of the application setContentView(R.layout.main_layout); + mNavigationView = (NavigationView) findViewById(R.id.nav_drawer_navigation_view); + LayoutInflater.from(this).inflate(R.layout.nav_drawer_list_header, mNavigationView); ButterKnife.inject(this); // Initializes the Toolbar. setSupportActionBar(mToolbar); + if (noInstantiatedExceptionReceived) { + TrackListPagerFragment pagerFragment = new TrackListPagerFragment(); + if (mNavigationView != null && mNavigationView.getMenu() != null) { + MenuItem menuItem = mNavigationView.getMenu().findItem(R.id.menu_nav_drawer_tracklist_new); + transitToFragment(menuItem, pagerFragment); + } + else { + LOGGER.warn("Could not re-create TrackListPagerFragment: mNavigationView="+ mNavigationView); + } + } + // Register a listener for a menu item that gets selected. mNavigationView.setNavigationItemSelectedListener(menuItem -> { @@ -310,17 +331,10 @@ public void onConfigurationChanged(Configuration newConfig) { protected void onDestroy() { super.onDestroy(); - Crouton.cancelAllCroutons(); - this.unregisterReceiver(errorInformationReceiver); mTemporaryFileManager.shutdown(); - // Unsubscribe all subscriptions. - if (mPreferenceSubscription != null) { - mPreferenceSubscription.unsubscribe(); - } - if (!subscriptions.isUnsubscribed()) { subscriptions.unsubscribe(); } @@ -489,7 +503,7 @@ private boolean selectDrawerItem(MenuItem menuItem) { startActivity(intent); return false; case R.id.menu_nav_drawer_settings_general: - Intent intent2 = new Intent(BaseMainActivity.this, NewSettingsActivity.class); + Intent intent2 = new Intent(BaseMainActivity.this, SettingsActivity.class); startActivity(intent2); return false; case R.id.menu_nav_drawer_settings_help: @@ -531,6 +545,13 @@ public void onPositive(MaterialDialog dialog) { if (fragment == null || isFragmentVisible(fragment.getClass().getSimpleName())) return false; + //now do the transition + transitToFragment(menuItem, fragment); + + return true; + } + + private void transitToFragment(MenuItem menuItem, Fragment fragment) { // Insert the fragment by replacing the existent fragment in the content frame. replaceFragment(fragment, selectedMenuItemID > menuItem.getItemId() ? @@ -545,8 +566,6 @@ public void onPositive(MaterialDialog dialog) { /// update the title of the toolbar. setTitle(menuItem.getTitle()); - - return true; } private void shutdownEnviroCar() { @@ -581,7 +600,7 @@ private void replaceFragment(Fragment fragment, int animIn, int animOut) { @Override public List getInjectionModules() { - return Arrays.asList(new InjectionActivityModule(this)); + return Arrays.asList(new MainActivityModule(this)); } @Subscribe @@ -592,22 +611,19 @@ public void onReceiveTrackFinishedEvent(final TrackFinishedEvent event) { mMainThreadWorker.schedule(() -> { if (event.mTrack == null) { // Track is null and thus there was an error. - Crouton.makeText(this, R.string.track_finishing_failed, Style.ALERT).show(); + showSnackbar(R.string.track_finishing_failed); } else try { - if (event.mTrack.getLastMeasurement() == null) { - // Track has no measurements - Crouton.makeText(this, R.string.track_finished_no_measurements, Style.ALERT) - .show(); - } else { + if (event.mTrack.getLastMeasurement() != null) { LOGGER.info("last is not null.. " + event.mTrack.getLastMeasurement() .toString()); + // Track has no measurements - Crouton.makeText(this, - getString(R.string.track_finished).concat(event.mTrack.getName()), - Style.INFO).show(); + showSnackbar(getString(R.string.track_finished).concat(event.mTrack.getName())); } } catch (NoMeasurementsException e) { - LOGGER.warn(e.getMessage(), e); + LOGGER.warn("Track has been finished without measurements", e); + // Track has no measurements + showSnackbar(R.string.track_finished_no_measurements); } }); } @@ -702,6 +718,18 @@ protected void onSaveInstanceState(Bundle outState) { outState.putSerializable(SEEN_ANNOUNCEMENTS, this.seenAnnouncements.toArray()); } + private void showSnackbar(int infoRes){ + showSnackbar(getString(infoRes)); + } + + private void showSnackbar(String info){ + mMainThreadWorker.schedule(() -> { + if (mDrawerLayout != null) { + Snackbar.make(mDrawerLayout, info, Snackbar.LENGTH_LONG).show(); + } + }); + } + private void readSavedState(Bundle savedInstanceState) { if (savedInstanceState == null) return; diff --git a/org.envirocar.app/src/org/envirocar/app/CommandListener.java b/org.envirocar.app/src/org/envirocar/app/CommandListener.java deleted file mode 100644 index c69dbc5c3..000000000 --- a/org.envirocar.app/src/org/envirocar/app/CommandListener.java +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app; - -import android.content.Context; -import android.preference.PreferenceManager; - -import com.squareup.otto.Bus; -import com.squareup.otto.Subscribe; - -import org.envirocar.app.handler.CarPreferenceHandler; -import org.envirocar.app.storage.DbAdapter; -import org.envirocar.app.handler.PreferenceConstants; -import org.envirocar.core.entity.Measurement; -import org.envirocar.core.events.gps.GpsDOPEvent; -import org.envirocar.core.exception.MeasurementSerializationException; -import org.envirocar.core.exception.TrackAlreadyFinishedException; -import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; -import org.envirocar.core.logging.Logger; -import org.envirocar.core.util.TrackMetadata; -import org.envirocar.obd.Collector; -import org.envirocar.obd.Listener; -import org.envirocar.obd.MeasurementListener; -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.commands.EngineLoad; -import org.envirocar.obd.commands.FuelSystemStatus; -import org.envirocar.obd.commands.IntakePressure; -import org.envirocar.obd.commands.IntakeTemperature; -import org.envirocar.obd.commands.LongTermTrimBank1; -import org.envirocar.obd.commands.MAF; -import org.envirocar.obd.commands.NumberResultCommand; -import org.envirocar.obd.commands.O2LambdaProbe; -import org.envirocar.obd.commands.RPM; -import org.envirocar.obd.commands.ShortTermTrimBank1; -import org.envirocar.obd.commands.Speed; -import org.envirocar.obd.commands.TPS; -import org.envirocar.obd.events.IntakePreasureUpdateEvent; -import org.envirocar.obd.events.IntakeTemperatureUpdateEvent; -import org.envirocar.obd.events.RPMUpdateEvent; -import org.envirocar.obd.events.SpeedUpdateEvent; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; - -/** - * Standalone listener class for OBDII commands. It provides all - * received processed commands through the {@link Bus}. - * - * @author matthes rieke - */ -public class CommandListener implements Listener, MeasurementListener { - // TODO change listener stuff - - private static final Logger logger = Logger.getLogger(CommandListener.class); - - private Collector collector; - - - private TrackMetadata obdDeviceMetadata; - - private boolean shutdownCompleted = false; - - private static int instanceCount; - private ExecutorService inserter = new ThreadPoolExecutor(1, 1, 0L, - TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), Executors - .defaultThreadFactory(), - new RejectedExecutionHandler() { - @Override - public void rejectedExecution(Runnable r, - ThreadPoolExecutor executor) { - logger.warn(String.format("Execution rejected: %s / %s", r.toString(), - executor.toString())); - } - - }); - - // Injected variables - @Inject - @InjectApplicationScope - protected Context mContext; - @Inject - protected Bus mBus; - @Inject - protected CarPreferenceHandler mCarManager; - @Inject - protected DbAdapter mDBAdapter; - - - public CommandListener(Context context) { - // First, inject all annotated fields. - ((Injector) context).injectObjects(this); - - // then register on the bus. - this.mBus.register(this); - - String samplingRate = PreferenceManager.getDefaultSharedPreferences - (context.getApplicationContext()).getString(PreferenceConstants.SAMPLING_RATE, null); - - int val; - if (samplingRate != null) { - try { - val = Integer.parseInt(samplingRate) * 1000; - } catch (NumberFormatException e) { - val = Collector.DEFAULT_SAMPLING_RATE_DELTA; - } - } else { - val = Collector.DEFAULT_SAMPLING_RATE_DELTA; - } - - this.collector = new Collector(mContext, this, mCarManager.getCar(), val); - -// EventBus.getInstance().registerListener(this.collector); - - synchronized (CommandListener.class) { - instanceCount++; - logger.debug("Initialized. Hash: " + System.identityHashCode(this) + "; active " + - "instances: " + instanceCount); - } - } - - @Subscribe - public void onReceiveGpsDOPEvent(GpsDOPEvent event) { - logger.info(String.format("Received event: %s", event.toString())); - if (collector != null) - collector.newDop(event.mDOP); - } - - public void receiveUpdate(CommonCommand command) { - // Get the name and the result of the Command - - if (!(command instanceof NumberResultCommand)) return; - - NumberResultCommand numberCommand = (NumberResultCommand) command; - - if (isNoDataCommand(command)) - return; - - /* - * Check which measurent is returned and save the value in the - * previously created measurement - */ - - // Speed - - if (command instanceof Speed) { - try { - Integer speedMeasurement = (Integer) numberCommand.getNumberResult().intValue(); - this.collector.newSpeed(speedMeasurement); - mBus.post(new SpeedUpdateEvent(speedMeasurement)); - logger.info("Processed Speed Response: " + speedMeasurement + " time: " + command - .getResultTime()); - } catch (NumberFormatException e) { - logger.warn("speed parse exception", e); - } - } - - //RPM - - else if (command instanceof RPM) { - - try { - Integer rpmMeasurement = (Integer) numberCommand.getNumberResult(); - this.collector.newRPM(rpmMeasurement); - mBus.post(new RPMUpdateEvent(rpmMeasurement)); -// logger.info("Processed RPM Response: "+rpmMeasurement +" time: "+command -// .getResultTime()); - } catch (NumberFormatException e) { - logger.warn("rpm parse exception", e); - } - } - - //IntakePressure - - else if (command instanceof IntakePressure) { - try { - Integer intakePressureMeasurement = (Integer) numberCommand.getNumberResult(); - this.collector.newIntakePressure(intakePressureMeasurement); - mBus.post(new IntakePreasureUpdateEvent(intakePressureMeasurement)); -// logger.info("Processed IAP Response: "+intakePressureMeasurement +" time: -// "+command.getResultTime()); - } catch (NumberFormatException e) { - logger.warn("Intake Pressure parse exception", e); - } - } - - //IntakeTemperature - - else if (command instanceof IntakeTemperature) { - try { - Integer intakeTemperatureMeasurement = (Integer) numberCommand.getNumberResult(); - this.collector.newIntakeTemperature(intakeTemperatureMeasurement); - this.mBus.post(new IntakeTemperatureUpdateEvent(intakeTemperatureMeasurement)); -// logger.info("Processed IAT Response: "+intakeTemperatureMeasurement +" time: -// "+command.getResultTime()); - } catch (NumberFormatException e) { - logger.warn("Intake Temperature parse exception", e); - } - } else if (command instanceof MAF) { - float mafMeasurement = (Float) numberCommand.getNumberResult(); - this.collector.newMAF(mafMeasurement); -// logger.info("Processed MAF Response: "+mafMeasurement +" time: "+command.getResultTime -// ()); - } else if (command instanceof TPS) { - int tps = (Integer) numberCommand.getNumberResult(); - this.collector.newTPS(tps); -// logger.info("Processed TPS Response: "+tps +" time: "+command.getResultTime()); - } else if (command instanceof EngineLoad) { - double load = (Float) numberCommand.getNumberResult(); - this.collector.newEngineLoad(load); -// logger.info("Processed EngineLoad Response: "+load +" time: "+command.getResultTime()); - } else if (command instanceof FuelSystemStatus) { - boolean loop = ((FuelSystemStatus) command).isInClosedLoop(); - int status = ((FuelSystemStatus) command).getStatus(); - this.collector.newFuelSystemStatus(loop, status); -// logger.info("Processed FuelSystemStatus Response: Closed? "+loop +" Status: "+ status -// +"; time: "+command.getResultTime()); - } else if (command instanceof O2LambdaProbe) { - this.collector.newLambdaProbeValue((O2LambdaProbe) command); -// logger.info("Processed O2LambdaProbe Response: "+ command.toString()); - } else if (command instanceof ShortTermTrimBank1) { - this.collector.newShortTermTrimBank1(((ShortTermTrimBank1) command).getNumberResult()); -// logger.info("Processed ShortTermTrimBank1: "+ command.toString()); - } else if (command instanceof LongTermTrimBank1) { - this.collector.newLongTermTrimBank1(((LongTermTrimBank1) command).getNumberResult()); -// logger.info("Processed LongTermTrimBank1: "+ command.toString()); - } - } - - - private boolean isNoDataCommand(CommonCommand command) { - if (command.getRawData() != null && (command.getRawData().equals("NODATA") || - command.getRawData().equals(""))) return true; - - if (command.getRawData() == null) return true; - - return false; - } - - - /** - * Helper method to insert track measurement into the database (ensures that - * track measurement is only stored every 5 seconds and not faster...) - * - * @param measurement The measurement you want to insert - */ - public void insertMeasurement(final Measurement measurement) { - logger.warn(String.format("Invoking insertion from Thread %s and CommandListener %s: %s", - Thread.currentThread().getId(), System.identityHashCode(CommandListener.this), - measurement)); - this.inserter.submit(new Runnable() { - @Override - public void run() { - try { - mDBAdapter.insertNewMeasurement(measurement); - } catch (TrackAlreadyFinishedException e) { - logger.warn(e.getMessage(), e); - } catch (MeasurementSerializationException e) { - logger.warn(e.getMessage(), e); - } - } - }); - } - - public void shutdown() { - logger.info("shutting down CommandListener. Hash: " + System.identityHashCode(this)); - if(!shutdownCompleted) - return; - - // Unregister from the eventbus. - mBus.unregister(this); - - this.inserter.shutdown(); - - synchronized (CommandListener.class) { - if (!this.shutdownCompleted) { - instanceCount--; - this.shutdownCompleted = true; - } - } - } - - @Override - public void onConnected(String deviceName) { - obdDeviceMetadata = new TrackMetadata(); - obdDeviceMetadata.putEntry(TrackMetadata.OBD_DEVICE, deviceName); - - mDBAdapter.setConnectedOBDDevice(obdDeviceMetadata); - } - -} diff --git a/org.envirocar.app/src/org/envirocar/app/injection/InjectionActivityModule.java b/org.envirocar.app/src/org/envirocar/app/MainActivityModule.java similarity index 60% rename from org.envirocar.app/src/org/envirocar/app/injection/InjectionActivityModule.java rename to org.envirocar.app/src/org/envirocar/app/MainActivityModule.java index 826dd2c58..da6f62e13 100644 --- a/org.envirocar.app/src/org/envirocar/app/injection/InjectionActivityModule.java +++ b/org.envirocar.app/src/org/envirocar/app/MainActivityModule.java @@ -1,93 +1,114 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.injection; - - -import android.app.Activity; -import android.content.Context; - -import org.envirocar.app.BaseMainActivity; -import org.envirocar.app.activity.StartStopButtonUtil; -import org.envirocar.app.handler.CarPreferenceHandler; -import org.envirocar.app.handler.TermsOfUseManager; -import org.envirocar.app.view.LogbookFragment; -import org.envirocar.app.view.RegisterFragment; -import org.envirocar.app.view.dashboard.DashboardMainFragment; -import org.envirocar.app.view.dashboard.RealDashboardFragment; -import org.envirocar.app.view.preferences.Tempomat; -import org.envirocar.core.injection.InjectionActivityScope; - -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; - -/** - * TODO JavaDoc - * - * @author dewall - */ -@Module( - injects = { - BaseMainActivity.class, - TermsOfUseManager.class, - CarPreferenceHandler.class, - LogbookFragment.class, - RegisterFragment.class, - StartStopButtonUtil.class, - RealDashboardFragment.class, - Tempomat.class, - DashboardMainFragment.class - }, - addsTo = InjectionApplicationModule.class, - library = true, - complete = false -) -public class InjectionActivityModule { - - private Activity mActivity; - - /** - * Constructor - * - * @param activity the activity of this scope. - */ - public InjectionActivityModule(Activity activity) { - this.mActivity = activity; - } - - - @Provides - public Activity provideActivity() { - return mActivity; - } - - @Provides - @InjectionActivityScope - public Context provideContext() { - return mActivity; - } - - @Provides - @Singleton - public RealDashboardFragment provideRealDashboardFragment(){ - return new RealDashboardFragment(); - } - -} \ No newline at end of file +/** + * Copyright (C) 2013 - 2015 the enviroCar community + * + * This file is part of the enviroCar app. + * + * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ +package org.envirocar.app; + + +import android.app.Activity; +import android.content.Context; + +import org.envirocar.app.activity.StartStopButtonUtil; +import org.envirocar.app.handler.CarPreferenceHandler; +import org.envirocar.app.handler.TermsOfUseManager; +import org.envirocar.app.view.LogbookFragment; +import org.envirocar.app.view.RegisterFragment; +import org.envirocar.app.view.dashboard.DashboardMainFragment; +import org.envirocar.app.view.dashboard.DashboardMapFragment; +import org.envirocar.app.view.dashboard.DashboardTempomatFragment; +import org.envirocar.app.view.dashboard.DashboardTrackDetailsFragment; +import org.envirocar.app.view.dashboard.DashboardTrackMapFragment; +import org.envirocar.app.view.dashboard.DashboardTrackSettingsFragment; +import org.envirocar.app.view.dashboard.RealDashboardFragment; +import org.envirocar.app.view.preferences.Tempomat; +import org.envirocar.app.view.tracklist.AbstractTrackListCardFragment; +import org.envirocar.app.view.tracklist.TrackListLocalCardFragment; +import org.envirocar.app.view.tracklist.TrackListPagerFragment; +import org.envirocar.app.view.tracklist.TrackListRemoteCardFragment; +import org.envirocar.app.views.ReactiveTermsOfUseDialog; +import org.envirocar.core.injection.InjectionActivityScope; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * TODO JavaDoc + * + * @author dewall + */ +@Module( + injects = { + BaseMainActivity.class, + TermsOfUseManager.class, + CarPreferenceHandler.class, + LogbookFragment.class, + RegisterFragment.class, + StartStopButtonUtil.class, + RealDashboardFragment.class, + Tempomat.class, + DashboardMainFragment.class, + LogbookFragment.class, + RegisterFragment.class, + DashboardTrackDetailsFragment.class, + DashboardTempomatFragment.class, + DashboardTrackSettingsFragment.class, + DashboardMapFragment.class, + DashboardTrackMapFragment.class, + TrackListPagerFragment.class, + AbstractTrackListCardFragment.class, + TrackListLocalCardFragment.class, + TrackListRemoteCardFragment.class, + ReactiveTermsOfUseDialog.class + }, + addsTo = BaseApplicationModule.class, + library = true, + complete = false +) +public class MainActivityModule { + + private Activity mActivity; + + /** + * Constructor + * + * @param activity the activity of this scope. + */ + public MainActivityModule(Activity activity) { + this.mActivity = activity; + } + + + @Provides + public Activity provideActivity() { + return mActivity; + } + + @Provides + @InjectionActivityScope + public Context provideContext() { + return mActivity; + } + + @Provides + @Singleton + public RealDashboardFragment provideRealDashboardFragment(){ + return new RealDashboardFragment(); + } + +} diff --git a/org.envirocar.app/src/org/envirocar/app/TrackHandler.java b/org.envirocar.app/src/org/envirocar/app/TrackHandler.java deleted file mode 100644 index 6a1d81039..000000000 --- a/org.envirocar.app/src/org/envirocar/app/TrackHandler.java +++ /dev/null @@ -1,559 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app; - -import android.app.Activity; -import android.content.Context; - -import com.squareup.otto.Bus; -import com.squareup.otto.Subscribe; - -import org.envirocar.app.activity.DialogUtil; -import org.envirocar.app.exception.NotAcceptedTermsOfUseException; -import org.envirocar.app.exception.NotLoggedInException; -import org.envirocar.app.exception.ServerException; -import org.envirocar.app.exception.TrackAlreadyUploadedException; -import org.envirocar.app.handler.BluetoothHandler; -import org.envirocar.app.handler.TermsOfUseManager; -import org.envirocar.app.handler.UploadManager; -import org.envirocar.app.handler.UserHandler; -import org.envirocar.app.storage.DbAdapter; -import org.envirocar.core.entity.TermsOfUse; -import org.envirocar.core.entity.Track; -import org.envirocar.core.entity.User; -import org.envirocar.core.events.TrackFinishedEvent; -import org.envirocar.core.exception.DataRetrievalFailureException; -import org.envirocar.core.exception.DataUpdateFailureException; -import org.envirocar.core.exception.NoMeasurementsException; -import org.envirocar.core.exception.NotConnectedException; -import org.envirocar.core.exception.TrackSerializationException; -import org.envirocar.core.exception.UnauthorizedException; -import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; -import org.envirocar.core.logging.Logger; -import org.envirocar.obd.events.BluetoothServiceStateChangedEvent; -import org.envirocar.obd.service.BluetoothServiceState; -import org.envirocar.remote.DAOProvider; -import org.envirocar.storage.EnviroCarDB; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import rx.Observable; -import rx.Scheduler; -import rx.Subscriber; -import rx.exceptions.OnErrorThrowable; -import rx.observables.BlockingObservable; -import rx.schedulers.Schedulers; - -/** - * @author de Wall - */ -public class TrackHandler { - private static final Logger LOGGER = Logger.getLogger(TrackHandler.class); - private static final String TRACK_MODE = "trackMode"; - - /** - * Callback interface for uploading a track. - */ - public interface TrackUploadCallback { - - void onUploadStarted(Track track); - - /** - * Called if the track has been successfully uploaded. - * - * @param track the track to upload. - */ - void onSuccessfulUpload(Track track); - - /** - * Called if an error occured during the upload routine. - * - * @param track the track that was intended to be uploaded. - * @param message the error message to be displayed within snackbar. - */ - void onError(Track track, String message); - } - - @Inject - @InjectApplicationScope - protected Context mContext; - @Inject - protected Bus mBus; - @Inject - protected DbAdapter mDBAdapter; - @Inject - protected EnviroCarDB mEnvirocarDB; - @Inject - protected BluetoothHandler mBluetoothHandler; - @Inject - protected DAOProvider mDAOProvider; - @Inject - protected UserHandler mUserManager; - @Inject - protected TermsOfUseManager mTermsOfUseManager; - - private Scheduler.Worker mBackgroundWorker = Schedulers.io().createWorker(); - - private BluetoothServiceState mBluetoothServiceState = BluetoothServiceState.SERVICE_STOPPED; - - - /** - * Constructor. - * - * @param context the context of the activity's scope. - */ - public TrackHandler(Context context) { - // Inject all annotated fields. - ((Injector) context).injectObjects(this); - } - - /** - * Deletes a track and returns true if the track has been successfully deleted. - * - * @param trackID the id of the track to delete. - * @return true if the track has been successfully deleted. - */ - public boolean deleteLocalTrack(Track.TrackId trackID) { - return deleteLocalTrack( - mEnvirocarDB.getTrack(trackID) - .subscribeOn(Schedulers.io()) - .toBlocking() - .first()); - } - - /** - * Deletes a track and returns true if the track has been successfully deleted. - * - * @param trackRef the reference of the track. - * @return true if the track has been successfully deleted. - */ - public boolean deleteLocalTrack(Track trackRef) { - LOGGER.info(String.format("deleteLocalTrack(id = %s)", trackRef.getTrackID().getId())); - - // Only delete the track if the track is a local track. - if (trackRef.isLocalTrack()) { - LOGGER.info("deleteLocalTrack(...): Track is a local track."); - mEnvirocarDB.deleteTrack(trackRef); - return true; - } - - LOGGER.warn("deleteLocalTrack(...): track is no local track. No deletion."); - return false; - } - - /** - * Invokes the deletion of a remote track. Once the remote track has been successfully - * deleted, this method also deletes the locally stored reference of that track. - * - * @param trackRef - * @return - * @throws UnauthorizedException - * @throws NotConnectedException - */ - public boolean deleteRemoteTrack(Track trackRef) throws UnauthorizedException, - NotConnectedException { - LOGGER.info(String.format("deleteRemoteTrack(id = %s)", trackRef.getTrackID().getId())); - - // Check whether this track is a remote track. - if (!trackRef.isRemoteTrack()) { - LOGGER.warn("Track reference to upload is no remote track."); - return false; - } - - // Delete the track first remote and then the local reference. - try { - mDAOProvider.getTrackDAO().deleteTrack(trackRef.getRemoteID()); - } catch (DataUpdateFailureException e) { - e.printStackTrace(); - } - - mEnvirocarDB.deleteTrack(trackRef); - - // Successfully deleted the remote track. - LOGGER.info("deleteRemoteTrack(): Successfully deleted the remote track."); - return true; - } - - public boolean deleteAllRemoteTracksLocally() { - LOGGER.info("deleteAllRemoteTracksLocally()"); - mEnvirocarDB.deleteAllRemoteTracks() - .subscribeOn(Schedulers.io()) - .toBlocking() - .first(); - return true; - } - - public Track getTrackByID(long trackId) { - return getTrackByID(new Track.TrackId(trackId)); - } - - /** - * - */ - public Track getTrackByID(Track.TrackId trackId) { - LOGGER.info(String.format("getTrackByID(%s)", trackId.toString())); - return mDBAdapter.getTrack(trackId); - } - - public Observable uploadAllTracksObservable() { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - try { - assertIsUserLoggedIn(); - assertHasAcceptedTermsOfUse(); - } catch (NotLoggedInException e) { - subscriber.onError(e); - subscriber.onCompleted(); - return; - } catch (NotAcceptedTermsOfUseException e) { - subscriber.onError(e); - subscriber.onCompleted(); - return; - } - } - }); - - } - - - private boolean assertIsUserLoggedIn() throws NotLoggedInException { - if (mUserManager.isLoggedIn()) { - return true; - } else { - throw new NotLoggedInException("Not Logged In"); - } - } - - public Observable uploadAllTracks() { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - subscriber.onStart(); - - // Before starting the upload, first check the login status and whether the user - // has accepted the terms of use. - if (!assertIsUserLoggedIn(subscriber) - || !assertHasAcceptedTermsOfUse(subscriber)) { - return; - } - - List allLocalTracks = mDBAdapter.getAllLocalTracks(); - - UploadManager uploadManager = new UploadManager(mContext); - for (Track track : allLocalTracks) { - if (!assertIsLocalTrack(track, subscriber)) { - LOGGER.warn(String.format("Track with id=%s is no local track", - track.getTrackID())); - allLocalTracks.remove(track); - } - } - - uploadManager.uploadTracks(allLocalTracks) - .subscribe(new Subscriber() { - @Override - public void onCompleted() { - subscriber.onCompleted(); - } - - @Override - public void onError(Throwable e) { - subscriber.onError(e); - } - - @Override - public void onNext(Track track) { - subscriber.onNext(track); - } - }); - } - }); - } - - private BlockingObservable asserHasAcceptedTermsOfUseObservable() { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - // First, try to get whether the user has accepted the terms of use. - final User user = mUserManager.getUser(); - boolean verified = false; - try { - verified = mTermsOfUseManager.verifyTermsUseOfVersion(user - .getTermsOfUseVersion()); - } catch (ServerException e) { - LOGGER.warn(e.getMessage(), e); - String infoText = mContext.getString(R.string.trackviews_server_error); - subscriber.onError(new NotAcceptedTermsOfUseException(infoText)); - } - } - }).toBlocking(); - } - - private boolean assertHasAcceptedTermsOfUse() throws NotAcceptedTermsOfUseException { - // First, try to get whether the user has accepted the terms of use. - final User user = mUserManager.getUser(); - boolean verified = false; - try { - verified = mTermsOfUseManager.verifyTermsUseOfVersion(user.getTermsOfUseVersion()); - } catch (ServerException e) { - LOGGER.warn(e.getMessage(), e); - String infoText = mContext.getString(R.string.trackviews_server_error); - throw new NotAcceptedTermsOfUseException(infoText); - } - - return verified; - } - - private boolean assertHasAcceptedTermsOfUse(Subscriber subscriber) { - // First, try to get whether the user has accepted the terms of use. - final User user = mUserManager.getUser(); - boolean verified = false; - try { - verified = mTermsOfUseManager.verifyTermsUseOfVersion(user.getTermsOfUseVersion()); - } catch (ServerException e) { - LOGGER.warn(e.getMessage(), e); - String infoText = mContext.getString(R.string.trackviews_server_error); - subscriber.onError(e); - return false; - } - - return verified; - } - - private boolean assertIsLocalTrack(Track track, Subscriber subscriber) { - // If the track is no local track, then popup a snackbar. - if (!track.isLocalTrack()) { - String infoText = String.format(mContext.getString(R.string - .trackviews_is_already_uploaded), track.getName()); - LOGGER.info(infoText); - subscriber.onError(new TrackAlreadyUploadedException(infoText)); - return false; - } - return true; - } - - private boolean assertIsUserLoggedIn(Subscriber subscriber) { - // If the user is not logged in, then skip the upload and popup a snackbar. - if (!mUserManager.isLoggedIn()) { - LOGGER.warn("Cannot upload track, because the user is not logged in"); - String infoText = mContext.getString(R.string.trackviews_not_logged_in); - subscriber.onError(new NotLoggedInException(infoText)); - return false; - } - return true; - } - - private boolean assertHasAcceptedTermsOfUse(TrackUploadCallback callback) { - // First, try to get whether the user has accepted the terms of use. - final User user = mUserManager.getUser(); - boolean verified = false; - try { - verified = mTermsOfUseManager.verifyTermsUseOfVersion(user.getTermsOfUseVersion()); - } catch (ServerException e) { - LOGGER.warn(e.getMessage(), e); - String infoText = mContext.getString(R.string.trackviews_server_error); - callback.onError(null, infoText); - return false; - } - - return verified; - } - - private boolean assertIsLocalTrack(Track track, TrackUploadCallback callback) { - // If the track is no local track, then popup a snackbar. - if (!track.isLocalTrack()) { - String infoText = String.format(mContext.getString(R.string - .trackviews_is_already_uploaded), track.getName()); - LOGGER.info(infoText); - callback.onError(track, infoText); - return false; - } - return true; - } - - private boolean assertIsUserLoggedIn(Track track, TrackUploadCallback callback) { - // If the user is not logged in, then skip the upload and popup a snackbar. - if (!mUserManager.isLoggedIn()) { - LOGGER.warn("Cannot upload track, because the user is not logged in"); - String infoText = mContext.getString(R.string.trackviews_not_logged_in); - callback.onError(track, infoText); - return false; - } - return true; - } - - // TODO REMOVE THIS ACTIVITY STUFF... unbelievable.. no structure! - public void uploadTrack(Activity activity, Track track, TrackUploadCallback callback) { - // If the track is no local track, then popup a snackbar. - if (!track.isLocalTrack()) { - String infoText = String.format(mContext.getString(R.string - .trackviews_is_already_uploaded), track.getName()); - LOGGER.info(infoText); - callback.onError(track, infoText); - return; - } - - // If the user is not logged in, then skip the upload and popup a snackbar. - if (!mUserManager.isLoggedIn()) { - LOGGER.warn("Cannot upload track, because the user is not logged in"); - String infoText = mContext.getString(R.string.trackviews_not_logged_in); - callback.onError(track, infoText); - return; - } - - // First, try to get whether the user has accepted the terms of use. - final User user = mUserManager.getUser(); - boolean verified = false; - try { - verified = mTermsOfUseManager.verifyTermsUseOfVersion(user.getTermsOfUseVersion()); - } catch (ServerException e) { - LOGGER.warn(e.getMessage(), e); - String infoText = mContext.getString(R.string.trackviews_server_error); - callback.onError(track, infoText); - return; - } - - // If the user has not accepted the terms of use, then show a dialog where he - // can accept the terms of use. - if (!verified) { - final TermsOfUse current; - try { - current = mTermsOfUseManager.getCurrentTermsOfUse(); - } catch (ServerException e) { - LOGGER.warn("This should never happen!", e); - callback.onError(track, "Terms Of Use not accepted."); - return; - } - - // Create a dialog with which the user can accept the terms of use. - DialogUtil.createTermsOfUseDialog(current, - user.getTermsOfUseVersion() == null, new DialogUtil - .PositiveNegativeCallback() { - - @Override - public void negative() { - LOGGER.info("User did not accept the ToU."); - callback.onError(track, mContext.getString(R.string - .terms_of_use_info)); - } - - @Override - public void positive() { - // If the user accepted the terms of use, then update this and - // finally upload the track. - mTermsOfUseManager.userAcceptedTermsOfUse(user, current - .getIssuedDate()); - new UploadManager(activity).uploadSingleTrack(track, callback); - } - - }, activity); - - return; - } else { - // Upload the track if everything is right. - new UploadManager(activity).uploadSingleTrack(track, callback); - } - } - - public Observable fetchRemoteTrackObservable(Track remoteTrack) { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - try { - subscriber.onNext(fetchRemoteTrack(remoteTrack)); - subscriber.onCompleted(); - } catch (NotConnectedException e) { - throw OnErrorThrowable.from(e); - } catch (DataRetrievalFailureException e) { - throw OnErrorThrowable.from(e); - } catch (UnauthorizedException e) { - throw OnErrorThrowable.from(e); - } - } - }); - } - - public Track fetchRemoteTrack(Track remoteTrack) throws NotConnectedException, - UnauthorizedException, DataRetrievalFailureException { - try { - Track downloadedTrack = mDAOProvider.getTrackDAO().getTrackById(remoteTrack - .getRemoteID()); - - // Deep copy... TODO improve this. - remoteTrack.setName(downloadedTrack.getName()); - remoteTrack.setDescription(downloadedTrack.getDescription()); - remoteTrack.setMeasurements(new ArrayList<>(downloadedTrack.getMeasurements())); - remoteTrack.setCar(downloadedTrack.getCar()); - remoteTrack.setTrackStatus(downloadedTrack.getTrackStatus()); - remoteTrack.setMetadata(downloadedTrack.getMetadata()); - - remoteTrack.setStartTime(downloadedTrack.getStartTime()); - remoteTrack.setEndTime(downloadedTrack.getEndTime()); - remoteTrack.setDownloadState(Track.DownloadState.DOWNLOADED); - } catch (NoMeasurementsException e) { - e.printStackTrace(); - } - - try { - mEnvirocarDB.insertTrack(remoteTrack); - } catch (TrackSerializationException e) { - e.printStackTrace(); - } - // mDBAdapter.insertTrack(remoteTrack, true); - return remoteTrack; - } - - /** - * Finishes the current track. On the one hand, the remoteService that handles the connection to - * the Bluetooth device gets closed and the track in the database gets finished. - */ - public void finishCurrentTrack() { - LOGGER.info("stopTrack()"); - - // Set the current remoteService state to SERVICE_STOPPING. - mBus.post(new BluetoothServiceStateChangedEvent(BluetoothServiceState.SERVICE_STOPPING)); - - // Schedule a new async task for closing the remoteService, finishing the current track, and - // finally fireing an event on the event bus. - mBackgroundWorker.schedule(() -> { - LOGGER.info("backgroundworker"); - // Stop the background remoteService that is responsible for the OBDConnection. - mBluetoothHandler.stopOBDConnectionService(); - - // Finish the current track. - final Track track = mDBAdapter.finishCurrentTrack(); - - // Fire a new TrackFinishedEvent on the event bus. - mBus.post(new TrackFinishedEvent(track)); - }); - } - - @Subscribe - public void onReceiveBluetoothServiceStateChangedEvent( - BluetoothServiceStateChangedEvent event) { - LOGGER.info(String.format("onReceiveBluetoothServiceStateChangedEvent: %s", - event.toString())); - mBluetoothServiceState = event.mState; - } - -} diff --git a/org.envirocar.app/src/org/envirocar/app/activity/DialogUtil.java b/org.envirocar.app/src/org/envirocar/app/activity/DialogUtil.java index 87fb30b0c..ab1605f4e 100644 --- a/org.envirocar.app/src/org/envirocar/app/activity/DialogUtil.java +++ b/org.envirocar.app/src/org/envirocar/app/activity/DialogUtil.java @@ -18,10 +18,6 @@ */ package org.envirocar.app.activity; -import org.envirocar.app.R; -import org.envirocar.core.entity.TermsOfUse; - -import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -29,7 +25,6 @@ import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.os.Bundle; -import android.text.Html; import android.text.SpannableString; import android.text.Spanned; import android.view.ContextThemeWrapper; @@ -38,6 +33,8 @@ import android.widget.CheckBox; import android.widget.TextView; +import org.envirocar.app.R; + public class DialogUtil { public static void createSingleChoiceItemsDialog(String title, String[] items, @@ -162,31 +159,6 @@ public void cancelled() { } - public static void createTermsOfUseDialog(TermsOfUse current, - boolean firstTime, DialogCallback callback, - Context context) { - createTitleMessageDialog(context.getResources().getString(R.string.terms_of_use_title), - createTermsOfUseMarkup(current, firstTime, context), callback, context); - } - - - private static Spanned createTermsOfUseMarkup(TermsOfUse current, - boolean firstTime, Context context) { - StringBuilder sb = new StringBuilder(); - - sb.append("

"); - if (!firstTime) { - sb.append(context.getString(R.string.terms_of_use_sorry)); - } - else { - sb.append(context.getString(R.string.terms_of_use_info)); - } - sb.append(":

"); - sb.append(current.getContents().replace("", "
")); - - return Html.fromHtml(sb.toString()); - } - private static class DoNotShowAgainAlertDialog extends AlertDialog { private TextView messageView; diff --git a/org.envirocar.app/src/org/envirocar/app/activity/StartStopButtonUtil.java b/org.envirocar.app/src/org/envirocar/app/activity/StartStopButtonUtil.java index 9f261974a..033662dd8 100644 --- a/org.envirocar.app/src/org/envirocar/app/activity/StartStopButtonUtil.java +++ b/org.envirocar.app/src/org/envirocar/app/activity/StartStopButtonUtil.java @@ -25,7 +25,7 @@ import org.envirocar.app.BaseMainActivity; import org.envirocar.app.R; -import org.envirocar.app.TrackHandler; +import org.envirocar.app.handler.TrackRecordingHandler; import org.envirocar.app.activity.DialogUtil.DialogCallback; import org.envirocar.app.handler.CarPreferenceHandler; import org.envirocar.obd.service.BluetoothServiceState; @@ -53,7 +53,7 @@ public class StartStopButtonUtil { @Inject protected CarPreferenceHandler mCarManager; @Inject - protected TrackHandler mTrackHandler; + protected TrackRecordingHandler mTrackRecordingHandler; private int trackMode; private BluetoothServiceState serviceState = BluetoothServiceState.SERVICE_STOPPED; @@ -186,7 +186,7 @@ private void createStopTrackDialog(final OnTrackModeChangeListener trackModeList new DialogUtil.PositiveNegativeCallback() { @Override public void positive() { - mTrackHandler.finishCurrentTrack(); + mTrackRecordingHandler.finishCurrentTrack(); trackModeListener.onTrackModeChange(BaseMainActivity.TRACK_MODE_SINGLE); } diff --git a/org.envirocar.app/src/org/envirocar/app/events/TrackDetailsProvider.java b/org.envirocar.app/src/org/envirocar/app/events/TrackDetailsProvider.java index 479b1eafd..c432dc8d0 100644 --- a/org.envirocar.app/src/org/envirocar/app/events/TrackDetailsProvider.java +++ b/org.envirocar.app/src/org/envirocar/app/events/TrackDetailsProvider.java @@ -104,6 +104,7 @@ public void onReceiveNewMeasurementEvent(NewMeasurementEvent event) { mNumMeasurements++; + // update computed features updateDistance(event.mMeasurement); updateAverageSpeed(event.mMeasurement); updatePathOverlay(event.mMeasurement); @@ -176,13 +177,15 @@ private void updateDistance(Measurement measurement) { * @param measurement */ private void updateAverageSpeed(Measurement measurement) { - mTotalSpeed += measurement.getProperty(Measurement.PropertyKey.SPEED); - mAvrgSpeed = (int) mTotalSpeed / mNumMeasurements; - mBus.post(provideAverageSpeed()); + if (measurement.hasProperty(Measurement.PropertyKey.SPEED)){ + mTotalSpeed += measurement.getProperty(Measurement.PropertyKey.SPEED); + mAvrgSpeed = (int) mTotalSpeed / mNumMeasurements; + mBus.post(provideAverageSpeed()); + } } - public void onOBDConnectionStopped() { + public void clear() { mMainThreadWorker.schedule(() -> { mTrackMapOverlay.clearPath(); mNumMeasurements = 0; diff --git a/org.envirocar.app/src/org/envirocar/app/exception/InvalidInputException.java b/org.envirocar.app/src/org/envirocar/app/exception/InvalidInputException.java new file mode 100644 index 000000000..ab532124a --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/exception/InvalidInputException.java @@ -0,0 +1,18 @@ +package org.envirocar.app.exception; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class InvalidInputException extends Exception { + + /** + * Constructor. + * + * @param msg the error message. + */ + public InvalidInputException(String msg){ + super(msg); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/view/utils/CurrencyInputView.java b/org.envirocar.app/src/org/envirocar/app/exception/NoOBDSocketConnectedException.java similarity index 73% rename from org.envirocar.app/src/org/envirocar/app/view/utils/CurrencyInputView.java rename to org.envirocar.app/src/org/envirocar/app/exception/NoOBDSocketConnectedException.java index e7a9d1b3b..2c53682dd 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/utils/CurrencyInputView.java +++ b/org.envirocar.app/src/org/envirocar/app/exception/NoOBDSocketConnectedException.java @@ -1,27 +1,35 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.view.utils; - -/** - * TODO JavaDoc - * - * @author dewall - */ -public class CurrencyInputView { -} +/* + * Copyright (C) 2013 - 2015 the enviroCar community + * + * This file is part of the enviroCar app. + * + * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ + +package org.envirocar.app.exception; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class NoOBDSocketConnectedException extends Exception{ + + /** + * Constructor. + */ + public NoOBDSocketConnectedException(){ + super("No OBD socket connection could be established."); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/exception/TracksException.java b/org.envirocar.app/src/org/envirocar/app/exception/TracksException.java deleted file mode 100644 index c140279b1..000000000 --- a/org.envirocar.app/src/org/envirocar/app/exception/TracksException.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.exception; - -import org.envirocar.app.storage.DbAdapter; - -/** - * This exception is thrown when there are no tracks in the local database. - * - * @author jakob - * @deprecated replaced by invariants of Interface {@link DbAdapter#getLastUsedTrack()} - */ -@Deprecated -public class TracksException extends Exception { - - /** - * - */ - private static final long serialVersionUID = 5754700912732803345L; - - @Deprecated - public TracksException(String e) { - super(e); - } - -} diff --git a/org.envirocar.app/src/org/envirocar/app/exception/UUIDSanityCheckFailedException.java b/org.envirocar.app/src/org/envirocar/app/exception/UUIDSanityCheckFailedException.java new file mode 100644 index 000000000..66bec20c5 --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/exception/UUIDSanityCheckFailedException.java @@ -0,0 +1,16 @@ +package org.envirocar.app.exception; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class UUIDSanityCheckFailedException extends Exception { + + /** + * Constructor. + */ + public UUIDSanityCheckFailedException() { + super("The UUID sanity check failed or is not supported?!"); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/handler/BluetoothHandler.java b/org.envirocar.app/src/org/envirocar/app/handler/BluetoothHandler.java index 7501bc123..e2b274342 100644 --- a/org.envirocar.app/src/org/envirocar/app/handler/BluetoothHandler.java +++ b/org.envirocar.app/src/org/envirocar/app/handler/BluetoothHandler.java @@ -1,24 +1,25 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.app.handler; import android.app.Activity; +import android.app.ActivityManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; @@ -36,17 +37,18 @@ import org.envirocar.core.events.bluetooth.BluetoothDeviceSelectedEvent; import org.envirocar.core.events.bluetooth.BluetoothStateChangedEvent; import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; import org.envirocar.core.utils.BroadcastUtils; import org.envirocar.core.utils.ServiceUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.inject.Inject; +import javax.inject.Singleton; import rx.Observable; import rx.Scheduler; @@ -57,16 +59,13 @@ /** * @author dewall */ +@Singleton public class BluetoothHandler { private static final Logger LOGGER = Logger.getLogger(BluetoothHandler.class); - // Injected variables. - @Inject - @InjectApplicationScope - protected Context mContext; - @Inject - protected Bus mBus; + private final Context context; + private final Bus bus; private final Scheduler.Worker mWorker = Schedulers.io().createWorker(); @@ -101,7 +100,7 @@ public void onReceive(Context context, Intent intent) { // Post a new event for the changed bluetooth state on the eventbus. BluetoothStateChangedEvent turnedOffEvent = new BluetoothStateChangedEvent(false); - mBus.post(turnedOffEvent); + bus.post(turnedOffEvent); break; case BluetoothAdapter.STATE_TURNING_ON: @@ -113,7 +112,7 @@ public void onReceive(Context context, Intent intent) { // Post a new event for the changed bluetooth state on the eventbus. BluetoothStateChangedEvent turnedOnEvent = new BluetoothStateChangedEvent(true); - mBus.post(turnedOnEvent); + bus.post(turnedOnEvent); break; default: @@ -130,35 +129,54 @@ public void onReceive(Context context, Intent intent) { * * @param context the context of the current scope. */ - public BluetoothHandler(Context context) { - // Inject ourselves. - ((Injector) context).injectObjects(this); + @Inject + public BluetoothHandler(@InjectApplicationScope Context context, Bus bus) { + this.context = context; + this.bus = bus; // Get the default bluetooth adapter. mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // Register ourselves on the eventbus. - mBus.register(this); + this.bus.register(this); // Register this handler class for Bluetooth State Changed broadcasts. IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - mContext.registerReceiver(mBluetoothStateChangedReceiver, filter); + this.context.registerReceiver(mBluetoothStateChangedReceiver, filter); } /** * Starts the connection to the bluetooth device if not already active. */ public void startOBDConnectionService() { - if (!ServiceUtils.isServiceRunning(mContext, OBDConnectionService.class)) - mContext.getApplicationContext() - .startService(new Intent(mContext, OBDConnectionService.class)); + if (!ServiceUtils.isServiceRunning(context, OBDConnectionService.class)) + context.getApplicationContext() + .startService(new Intent(context, OBDConnectionService.class)); } public void stopOBDConnectionService() { - if (ServiceUtils.isServiceRunning(mContext, OBDConnectionService.class)) - mContext.getApplicationContext() - .stopService(new Intent(mContext, OBDConnectionService.class)); + if (ServiceUtils.isServiceRunning(context, OBDConnectionService.class)) { + context.getApplicationContext() + .stopService(new Intent(context, OBDConnectionService.class)); + } + + ActivityManager amgr = (ActivityManager) context.getSystemService(Context + .ACTIVITY_SERVICE); + + List list = amgr.getRunningAppProcesses(); + if (list != null) { + for (int i = 0; i < list.size(); i++) { + ActivityManager.RunningAppProcessInfo apinfo = list.get(i); + + String[] pkgList = apinfo.pkgList; + if (apinfo.processName.startsWith("org.envirocar.app.services.OBD")) { + for (int j = 0; j < pkgList.length; j++) { + amgr.killBackgroundProcesses(pkgList[j]); + } + } + } + } } @@ -174,7 +192,7 @@ public BluetoothDevice getSelectedBluetoothDevice() { return null; // Get the preferences of the device. - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); String deviceName = preferences.getString( PreferenceConstants.PREF_BLUETOOTH_NAME, PreferenceConstants.PREF_EMPTY); @@ -199,7 +217,7 @@ public BluetoothDevice getSelectedBluetoothDevice() { } public void setSelectedBluetoothDevice(BluetoothDevice selectedDevice) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); boolean success = preferences.edit() .remove(PreferenceConstants.PREF_BLUETOOTH_NAME) @@ -218,7 +236,7 @@ public void setSelectedBluetoothDevice(BluetoothDevice selectedDevice) { if (success) { LOGGER.info("Successfully updated shared preferences"); - mBus.post(new BluetoothDeviceSelectedEvent(selectedDevice)); + bus.post(new BluetoothDeviceSelectedEvent(selectedDevice)); } } @@ -316,8 +334,15 @@ public Observable startBluetoothDiscovery() { // If the device is already discovering, cancel the discovery before starting. if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); - } + // Small timeout such that the broadcast receiver does not receive the first + // ACTION_DISCOVERY_FINISHED + try { + wait(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } if (mDiscoverySubscription != null) { // Cancel the pending subscription. @@ -332,9 +357,8 @@ public Observable startBluetoothDiscovery() { filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); mDiscoverySubscription = BroadcastUtils - .createBroadcastObservable(mContext, filter) + .createBroadcastObservable(context, filter) .subscribe(new Subscriber() { - private Subscription thisSubscription; @Override public void onCompleted() { @@ -356,7 +380,6 @@ public void onNext(Intent intent) { // If the discovery process has been started. if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) { subscriber.onStart(); - thisSubscription = mDiscoverySubscription; } // If the discovery process finds a device @@ -372,13 +395,15 @@ else if (BluetoothDevice.ACTION_FOUND.equals(action)) { else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { subscriber.onCompleted(); mWorker.schedule(() -> { - if (!thisSubscription.isUnsubscribed()) - thisSubscription.unsubscribe(); - }, 100, TimeUnit.MILLISECONDS); + if (!isUnsubscribed()) { + unsubscribe(); + } + }, 100, TimeUnit.MILLISECONDS); } } }); + subscriber.add(mDiscoverySubscription); mBluetoothAdapter.startDiscovery(); }); } @@ -425,7 +450,7 @@ public void startBluetoothDeviceDiscovery(final boolean filterPairedDevices, filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); // Register a receiver. - mContext.registerReceiver(new BroadcastReceiver() { + context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -454,7 +479,7 @@ else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) { // If the discovery process has been finished. else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { callback.onActionDeviceDiscoveryFinished(); - mContext.unregisterReceiver(this); + BluetoothHandler.this.context.unregisterReceiver(this); } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { // Nothing to do yet } @@ -541,7 +566,7 @@ public void pairDevice(final BluetoothDevice device, // Register a new BroadcastReceiver for BOND_STATE_CHANGED actions. IntentFilter intent = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - mContext.registerReceiver(new BroadcastReceiver() { + context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -603,7 +628,7 @@ public void unpairDevice(final BluetoothDevice device, final BluetoothDeviceUnpa // Register a new BroadcastReceiver for BOND_STATE_CHANGED actions. IntentFilter intent = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - mContext.registerReceiver(new BroadcastReceiver() { + context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -621,10 +646,10 @@ public void onReceive(Context context, Intent intent) { prevState == BluetoothDevice.BOND_BONDED) { // The device has been successfully unpaired, inform the callback about this callback.onDeviceUnpaired(device); - mContext.unregisterReceiver(this); + BluetoothHandler.this.context.unregisterReceiver(this); } else if (state == BluetoothDevice.ERROR) { callback.onUnpairingError(device); - mContext.unregisterReceiver(this); + BluetoothHandler.this.context.unregisterReceiver(this); } } } diff --git a/org.envirocar.app/src/org/envirocar/app/handler/CarPreferenceHandler.java b/org.envirocar.app/src/org/envirocar/app/handler/CarPreferenceHandler.java index f69f53eed..0e68c0dd1 100644 --- a/org.envirocar.app/src/org/envirocar/app/handler/CarPreferenceHandler.java +++ b/org.envirocar.app/src/org/envirocar/app/handler/CarPreferenceHandler.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -25,67 +25,82 @@ import android.widget.Toast; import com.squareup.otto.Bus; +import com.squareup.otto.Subscribe; -import org.envirocar.remote.DAOProvider; import org.envirocar.core.ContextInternetAccessProvider; import org.envirocar.core.entity.Car; +import org.envirocar.core.entity.Track; import org.envirocar.core.events.NewCarTypeSelectedEvent; +import org.envirocar.core.events.NewUserSettingsEvent; import org.envirocar.core.exception.DataCreationFailureException; import org.envirocar.core.exception.NotConnectedException; import org.envirocar.core.exception.UnauthorizedException; import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; import org.envirocar.core.utils.CarUtils; +import org.envirocar.remote.DAOProvider; +import org.envirocar.storage.EnviroCarDB; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import javax.inject.Inject; +import javax.inject.Singleton; + +import rx.Observable; +import rx.Subscriber; /** * The manager for cars. */ +@Singleton public class CarPreferenceHandler { private static final Logger LOG = Logger.getLogger(CarPreferenceHandler.class); + private static final String PREFERENCE_TAG_DOWNLOADED = "cars_downloaded"; - @Inject - @InjectApplicationScope - protected Context mContext; - @Inject - protected Bus mBus; - @Inject - protected UserHandler mUserManager; - @Inject - protected DAOProvider mDAOProvider; + private final Context mContext; + private final Bus mBus; + private final UserHandler mUserManager; + private final DAOProvider mDAOProvider; + private final EnviroCarDB mEnviroCarDB; + private final SharedPreferences mSharedPreferences; private Car mSelectedCar; private Set mDeserialzedCars; private Set mSerializedCarStrings; + private Map temporaryAlreadyRegisteredCars = new HashMap<>(); /** * Constructor. * * @param context the context of the activity or application. */ - public CarPreferenceHandler(Context context) { - // Inject all annotated fields. - ((Injector) context).injectObjects(this); - - // get the default PreferenceManager - final SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(context); - - mSelectedCar = CarUtils.instantiateCar(preferences.getString(PreferenceConstants + @Inject + public CarPreferenceHandler(@InjectApplicationScope Context context, Bus bus, UserHandler + userManager, DAOProvider daoProvider, EnviroCarDB enviroCarDB, + SharedPreferences sharedPreferences) { + this.mContext = context; + this.mBus = bus; + this.mUserManager = userManager; + this.mDAOProvider = daoProvider; + this.mEnviroCarDB = enviroCarDB; + this.mSharedPreferences = sharedPreferences; + + // no unregister required because it is applications scoped. + this.mBus.register(this); + + mSelectedCar = CarUtils.instantiateCar(sharedPreferences.getString(PreferenceConstants .PREFERENCE_TAG_CAR, null)); // Get the serialized car strings of all added cars. - mSerializedCarStrings = preferences + mSerializedCarStrings = sharedPreferences .getStringSet(PreferenceConstants.PREFERENCE_TAG_CARS, new HashSet<>()); // Instantiate the cars from the set of serialized strings. @@ -101,6 +116,81 @@ public CarPreferenceHandler(Context context) { } } + public Observable> getAllDeserializedCars() { + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + subscriber.onStart(); + subscriber.onNext(getDeserialzedCars()); + subscriber.onCompleted(); + } + }); + } + + public Observable> downloadRemoteCarsOfUser() { + return Observable.just(mUserManager.getUser()) + .flatMap(user -> mDAOProvider.getSensorDAO().getCarsByUserObservable(user)) + .map(cars -> { + LOG.info(String.format( + "Successfully downloaded %s remote cars. Add these to the preferences.", + cars.size())); + for (Car car : cars) { + addCar(car); + } + setIsDownloaded(true); + return cars; + }); + } + + public Observable assertTemporaryCar(Car car) { + LOG.info("getUploadedCarReference()"); + return Observable.just(car) + .flatMap(car1 -> { + // If the car is already uploaded, then just return car instance. + if (CarUtils.isCarUploaded(car1)) + return Observable.just(car1); + + // the car is already uploaded before but the car has not the right remote id + if (temporaryAlreadyRegisteredCars.containsKey(car1.getId())) { + car1.setId(temporaryAlreadyRegisteredCars.get(car1.getId())); + return Observable.just(car1); + } + + // create a new car instance. + return registerCar(car1); + }); + } + + private Observable registerCar(Car car) { + LOG.info(String.format("registerCarBeforeUpload(%s)", car.toString())); + String oldID = car.getId(); + return mDAOProvider.getSensorDAO() + // Create a new remote car and update the car remote id. + .createCarObservable(car) + // update all IDs of tracks that have this car as a reference + .flatMap(updCar -> updateCarIDsOfTracksObservable(oldID, updCar)) + // sum all tracks to a list of tracks. + .toList() + // Just set the current car reference to the updated one and return it. + .map(tracks -> { + if (!temporaryAlreadyRegisteredCars.containsKey(oldID)) + temporaryAlreadyRegisteredCars.put(oldID, car.getId()); + if (getCar().getId().equals(oldID)) + setCar(car); + return car; + }); + } + + private Observable updateCarIDsOfTracksObservable(String oldID, Car car) { + return mEnviroCarDB.getAllTracksByCar(car.getId(), true) + .flatMap(tracks -> Observable.from(tracks)) + .map(track -> { + track.setCar(car); + return track; + }) + .concatMap(track -> mEnviroCarDB.updateTrackObservable(track)); + } + /** * Adds the car to the set of cars in the shared preferences. * @@ -218,8 +308,11 @@ public int compare(Car lhs, Car rhs) { */ public void registerCarAtServer(final Car car) { try { - if (car.getFuelType() == null || car.getManufacturer() == null || car.getModel() == - null || car.getConstructionYear() == 0 || car.getEngineDisplacement() == 0) + if (car.getFuelType() == null || + car.getManufacturer() == null || + car.getModel() == null || + car.getConstructionYear() == 0 || + car.getEngineDisplacement() == 0) throw new Exception("Empty value!"); if (car.getManufacturer().isEmpty() || car.getModel().isEmpty()) { throw new Exception("Empty value!"); @@ -231,8 +324,8 @@ public void registerCarAtServer(final Car car) { return; } - if (new ContextInternetAccessProvider(mContext).isConnected() - && mUserManager.isLoggedIn()) { + if (new ContextInternetAccessProvider(mContext).isConnected() && + mUserManager.isLoggedIn()) { new AsyncTask() { @Override @@ -263,6 +356,14 @@ protected Void doInBackground(Void... params) { } } + @Subscribe + public void onReceiveNewUserSettingsEvent(NewUserSettingsEvent event) { + LOG.info("Received NewUserSettingsEvent: " + event.toString()); + if (!event.mIsLoggedIn) { + setIsDownloaded(false); + LOG.info("Downloaded setted to false"); + } + } /** * Getter method for the serialized car strings. @@ -282,7 +383,7 @@ private void flushCarListState() { // Recreate serialized car strings. mSerializedCarStrings.clear(); - for(Car car : mDeserialzedCars){ + for (Car car : mDeserialzedCars) { mSerializedCarStrings.add(CarUtils.serializeCar(car)); } @@ -328,16 +429,27 @@ private void flushSelectedCarState() { .commit(); if (insertSuccess) - LOG.info("flushSelectedCarState(): Successfully inserted into shared " + - "preferences"); + LOG.info("flushSelectedCarState(): Successfully inserted into shared preferences"); else LOG.severe("flushSelectedCarState(): Error on insert."); } } + public void setIsDownloaded(boolean isDownloaded) { + LOG.info(String.format("setIsDownloaded() to [%s]", isDownloaded)); + mSharedPreferences.edit().remove(PREFERENCE_TAG_DOWNLOADED).commit(); + if (isDownloaded) { + mSharedPreferences.edit().putBoolean(PREFERENCE_TAG_DOWNLOADED, isDownloaded).commit(); + } + } + + public boolean isDownloaded() { + return mSharedPreferences.getBoolean(PREFERENCE_TAG_DOWNLOADED, false); + } + private boolean removeSelectedCarState() { // Delete the entry of the selected car and its hash code. - return PreferenceManager.getDefaultSharedPreferences(mContext).edit() + return mSharedPreferences.edit() .remove(PreferenceConstants.PREFERENCE_TAG_CAR) .remove(PreferenceConstants.CAR_HASH_CODE) .commit(); diff --git a/org.envirocar.app/src/org/envirocar/app/handler/CarRemoteListCache.java b/org.envirocar.app/src/org/envirocar/app/handler/CarRemoteListCache.java new file mode 100644 index 000000000..0e78a5b7f --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/handler/CarRemoteListCache.java @@ -0,0 +1,51 @@ +package org.envirocar.app.handler; + +import android.content.SharedPreferences; + +import org.envirocar.core.logging.Logger; +import org.envirocar.remote.DAOProvider; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * TODO JavaDoc + * + * @author dewall + */ +@Singleton +public class CarRemoteListCache { + private static final Logger LOG = Logger.getLogger(CarRemoteListCache.class); + + private static final String TAG_HAS_LIST = "cache_has_list"; + private static final String TAG_CARLIST = "cache_car_list"; + + private final SharedPreferences sharedPreferences; + private final DAOProvider daoProvider; + + @Inject + public CarRemoteListCache(SharedPreferences sharedPreferences, DAOProvider daoProvider){ + this.sharedPreferences = sharedPreferences; + this.daoProvider = daoProvider; + } + +// public Observable> getCachedCars(){ +// return Observable.just(sharedPreferences.getBoolean(TAG_HAS_LIST, false)) +// .flatMap(new Func1>() { +// @Override +// public Observable call(Boolean aBoolean) { +// if(aBoolean && sharedPreferences.get){ +// +// } else { +// +// } +// } +// }); +// return Observable.create(new Observable.OnSubscribe>() { +// @Override +// public void call(Subscriber> subscriber) { +// +// } +// }); +// } +} diff --git a/org.envirocar.app/src/org/envirocar/app/handler/HandlerModule.java b/org.envirocar.app/src/org/envirocar/app/handler/HandlerModule.java new file mode 100644 index 000000000..fa13e6a5b --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/handler/HandlerModule.java @@ -0,0 +1,36 @@ +package org.envirocar.app.handler; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * TODO JavaDOc + * + * @author dewall + */ +@Module( + complete = false, + library = true, + injects = { + BluetoothHandler.class, + CarPreferenceHandler.class, + LocationHandler.class, + TemporaryFileManager.class, + TermsOfUseManager.class, + TrackDAOHandler.class, + TrackRecordingHandler.class, + TrackUploadHandler.class, + UserHandler.class + } +) +public class HandlerModule { + + @Provides + @Singleton + org.envirocar.core.UserManager provideUserManagerImpl(UserHandler userHandler) { + return userHandler; + } + +} diff --git a/org.envirocar.app/src/org/envirocar/app/handler/LocationHandler.java b/org.envirocar.app/src/org/envirocar/app/handler/LocationHandler.java index f4c7d4d45..83d80f232 100644 --- a/org.envirocar.app/src/org/envirocar/app/handler/LocationHandler.java +++ b/org.envirocar.app/src/org/envirocar/app/handler/LocationHandler.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -32,19 +32,22 @@ import org.envirocar.core.events.gps.GpsDOP; import org.envirocar.core.events.gps.GpsDOPEvent; +import org.envirocar.core.events.gps.GpsLocationChangedEvent; import org.envirocar.core.events.gps.GpsSatelliteFix; import org.envirocar.core.events.gps.GpsSatelliteFixEvent; import org.envirocar.core.events.gps.GpsStateChangedEvent; -import org.envirocar.core.events.gps.GpsLocationChangedEvent; import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; import javax.inject.Inject; +import javax.inject.Singleton; /** + * TODO JavaDoc + * * @author dewall */ +@Singleton public class LocationHandler { private static final Logger LOGGER = Logger.getLogger(LocationHandler.class); private static final int MAX_TIMEFRAME = 1000 * 60; @@ -167,11 +170,8 @@ private Double parseDopString(String string) { }; // Injected variables. - @Inject - @InjectApplicationScope - protected Context mContext; - @Inject - protected Bus mBus; + private final Context mContext; + private final Bus mBus; private LocationManager mLocationManager; @@ -193,9 +193,10 @@ private Double parseDopString(String string) { * * @param context the context of the current scope. */ - public LocationHandler(Context context) { - // Inject ourselves and register on the bus. - ((Injector) context).injectObjects(this); + @Inject + public LocationHandler(@InjectApplicationScope Context context, Bus bus) { + this.mContext = context; + this.mBus = bus; // Sets the current Location updates to null. this.mLastLocationUpdate = null; @@ -299,7 +300,7 @@ public void onReceive(Context context, Intent intent) { // get the current gps state. boolean isActivated = isGPSEnabled(); // if the previous state is different to the current state, then fire a new event. - if(previousState != isActivated){ + if (previousState != isActivated) { mBus.post(new GpsStateChangedEvent(isActivated)); previousState = isActivated; } diff --git a/org.envirocar.app/src/org/envirocar/app/handler/PreferenceConstants.java b/org.envirocar.app/src/org/envirocar/app/handler/PreferenceConstants.java index 17856543e..1def86332 100644 --- a/org.envirocar.app/src/org/envirocar/app/handler/PreferenceConstants.java +++ b/org.envirocar.app/src/org/envirocar/app/handler/PreferenceConstants.java @@ -35,6 +35,8 @@ public interface PreferenceConstants { String PREF_BLUETOOTH_AUTOCONNECT = "pref_bluetooth_autoconnect"; String PREF_BLUETOOTH_DISCOVERY_INTERVAL = "pref_bluetooth_discovery_interval"; + String PREF_ENABLE_DIESE_CONSUMPTION = "pref_enable_diesel_consumption"; + String PREF_TEXT_TO_SPEECH = "pref_text_to_speech"; int DEFAULT_BLUETOOTH_DISCOVERY_INTERVAL = 60; diff --git a/org.envirocar.app/src/org/envirocar/app/handler/PreferencesHandler.java b/org.envirocar.app/src/org/envirocar/app/handler/PreferencesHandler.java index dc9c711ee..79423dc88 100644 --- a/org.envirocar.app/src/org/envirocar/app/handler/PreferencesHandler.java +++ b/org.envirocar.app/src/org/envirocar/app/handler/PreferencesHandler.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -72,23 +72,56 @@ public static Observable getDisplayStaysActiveObservable(Context contex .asObservable(); } - public static boolean isTextToSpeechEnabled(Context context){ + public static boolean isTextToSpeechEnabled(Context context) { return getSharedPreferences(context) .getBoolean(PREF_TEXT_TO_SPEECH, DEFAULT_TEXT_TO_SPEECH); } - public static Observable getTextToSpeechObservable(Context context){ + public static Observable getTextToSpeechObservable(Context context) { return getRxSharedPreferences(context) .getBoolean(PREF_TEXT_TO_SPEECH, DEFAULT_TEXT_TO_SPEECH) .asObservable(); } + private static RxSharedPreferences getRxSharedPreferences(Context context) { + return RxSharedPreferences.create(getSharedPreferences(context)); + } + + public static Long getSamplingRate(Context context) { + return Long.parseLong(getSharedPreferences(context) + .getString(SAMPLING_RATE, "5")); + } + + public static Observable getRxSharedSamplingRate(Context context) { + return getRxSharedPreferences(context) + .getString(SAMPLING_RATE, "5") + .asObservable().map(s -> Long.parseLong(s)); + } + + public static boolean isObfuscationEnabled(Context context) { + return getSharedPreferences(context) + .getBoolean(PreferenceConstants.OBFUSCATE_POSITION, false); + } + + public static Observable getObfuscationObservable(Context context){ + return getRxSharedPreferences(context) + .getBoolean(OBFUSCATE_POSITION, false) + .asObservable(); + } + public static SharedPreferences getSharedPreferences(Context context) { Preconditions.checkNotNull(context, "Input context cannot be null."); return PreferenceManager.getDefaultSharedPreferences(context); } - private static RxSharedPreferences getRxSharedPreferences(Context context) { - return RxSharedPreferences.create(getSharedPreferences(context)); + public static boolean isDieselConsumptionEnabled(Context context){ + return getSharedPreferences(context) + .getBoolean(PREF_ENABLE_DIESE_CONSUMPTION, false); + } + + public static Observable getDieselConsumptionObservable(Context context){ + return getRxSharedPreferences(context) + .getBoolean(PREF_ENABLE_DIESE_CONSUMPTION, false) + .asObservable(); } } diff --git a/org.envirocar.app/src/org/envirocar/app/handler/TemporaryFileManager.java b/org.envirocar.app/src/org/envirocar/app/handler/TemporaryFileManager.java index 4722ad2f3..9cd43c160 100644 --- a/org.envirocar.app/src/org/envirocar/app/handler/TemporaryFileManager.java +++ b/org.envirocar.app/src/org/envirocar/app/handler/TemporaryFileManager.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -21,7 +21,6 @@ import android.content.Context; import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; import org.envirocar.core.util.Util; @@ -31,53 +30,55 @@ import java.util.UUID; import javax.inject.Inject; +import javax.inject.Singleton; /** + * TODO JavaDoc * + * @author dewall */ +@Singleton public class TemporaryFileManager { private static final Logger logger = Logger.getLogger(TemporaryFileManager.class); - @Inject - @InjectApplicationScope - protected Context mContext; - - private List temporaryFiles = new ArrayList(); + private final Context context; + private final List temporaryFiles = new ArrayList(); /** * Constructor that only injects the variables. * - * @param context the application context. + * @param context the application context. */ - public TemporaryFileManager(Context context) { - ((Injector) context).injectObjects(this); - } + @Inject + public TemporaryFileManager(@InjectApplicationScope Context context) { + this.context = context; + } /** * */ - public void shutdown() { - for (File f : this.temporaryFiles) { - try { - f.delete(); - } catch (RuntimeException e) { - logger.warn(e.getMessage(), e); - } - } - } + public void shutdown() { + for (File f : this.temporaryFiles) { + try { + f.delete(); + } catch (RuntimeException e) { + logger.warn(e.getMessage(), e); + } + } + } + + public File createTemporaryFile() { + File result = new File(Util.resolveCacheFolder(context), UUID.randomUUID().toString()); + + addTemporaryFile(result); - public File createTemporaryFile() { - File result = new File(Util.resolveCacheFolder(mContext), UUID.randomUUID().toString()); - - addTemporaryFile(result); - - return result; - } + return result; + } - private synchronized void addTemporaryFile(File result) { - this.temporaryFiles .add(result); - } + private synchronized void addTemporaryFile(File result) { + this.temporaryFiles.add(result); + } } diff --git a/org.envirocar.app/src/org/envirocar/app/handler/TermsOfUseManager.java b/org.envirocar.app/src/org/envirocar/app/handler/TermsOfUseManager.java index 4c790ed84..e12cc64a0 100644 --- a/org.envirocar.app/src/org/envirocar/app/handler/TermsOfUseManager.java +++ b/org.envirocar.app/src/org/envirocar/app/handler/TermsOfUseManager.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -25,10 +25,10 @@ import com.squareup.otto.Bus; import org.envirocar.app.R; -import org.envirocar.app.activity.DialogUtil; -import org.envirocar.app.activity.DialogUtil.PositiveNegativeCallback; +import org.envirocar.app.exception.NotAcceptedTermsOfUseException; +import org.envirocar.app.exception.NotLoggedInException; import org.envirocar.app.exception.ServerException; -import org.envirocar.remote.DAOProvider; +import org.envirocar.app.views.ReactiveTermsOfUseDialog; import org.envirocar.core.entity.TermsOfUse; import org.envirocar.core.entity.User; import org.envirocar.core.exception.DataRetrievalFailureException; @@ -36,18 +36,24 @@ import org.envirocar.core.exception.NotConnectedException; import org.envirocar.core.exception.UnauthorizedException; import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; +import org.envirocar.remote.DAOProvider; import java.util.List; import javax.inject.Inject; +import javax.inject.Singleton; -import de.keyboardsurfer.android.widget.crouton.Crouton; -import de.keyboardsurfer.android.widget.crouton.Style; +import rx.Observable; import rx.exceptions.OnErrorThrowable; import rx.functions.Func1; +/** + * TODO JavaDoc + * + * @author dewall + */ +@Singleton public class TermsOfUseManager { private static final Logger LOGGER = Logger.getLogger(TermsOfUseManager.class); // Mutex for locking when downloading. @@ -55,87 +61,187 @@ public class TermsOfUseManager { protected List list; // Injected variables. + private final Context mContext; + private final Bus mBus; + private final UserHandler mUserManager; + private final DAOProvider mDAOProvider; + + private TermsOfUse current; + + /** + * Constructor. + * + * @param context + */ @Inject - @InjectApplicationScope - protected Context mContext; - @Inject - protected Bus mBus; - @Inject - protected UserHandler mUserManager; - @Inject - protected DAOProvider mDAOProvider; + public TermsOfUseManager(@InjectApplicationScope Context context, Bus bus, UserHandler + userHandler, DAOProvider daoProvider) { + this.mContext = context; + this.mBus = bus; + this.mUserManager = userHandler; + this.mDAOProvider = daoProvider; + } + + public Observable verifyTermsOfUse(Activity activity) { + LOGGER.info("verifyTermsOfUse()"); + return getCurrentTermsOfUseObservable() + .flatMap(checkTermsOfUseAcceptance(activity)); + } + public Observable verifyTermsOfUse(Activity activity, T t) { + return verifyTermsOfUse(activity) + .map(termsOfUse -> { + LOGGER.info("User has accepted terms of use."); + return t; + }); + } - private TermsOfUse current; + public Observable getCurrentTermsOfUseObservable() { + LOGGER.info("getCurrentTermsOfUseObservable()"); + return current != null ? Observable.just(current) : getRemoteTermsOfUseObservable(); + } - public TermsOfUseManager(Context context) { + private Observable getRemoteTermsOfUseObservable() { + LOGGER.info("getRemoteTermsOfUse() TermsOfUse are null. Try to fetch the last TermsOfUse."); + return mDAOProvider.getTermsOfUseDAO() + .getAllTermsOfUseObservable() + .map(termsOfUses -> { + if (termsOfUses == null || termsOfUses.isEmpty()) + throw OnErrorThrowable.from(new NotConnectedException( + "Error while retrieving terms of use: " + + "Result set was null or empty")); - // Inject ourselves. - ((Injector) context).injectObjects(this); + // Set the list of terms of uses. + TermsOfUseManager.this.list = termsOfUses; - // try { - // retrieveTermsOfUse(); - // } catch (ServerException e) { - // LOGGER.warn(e.getMessage(), e); - // } + try { + // Get the id of the first terms of use instance and fetch + // the terms of use + String id = termsOfUses.get(0).getId(); + TermsOfUse inst = mDAOProvider.getTermsOfUseDAO().getTermsOfUse(id); + return inst; + } catch (DataRetrievalFailureException | NotConnectedException e) { + LOGGER.warn(e.getMessage(), e); + throw OnErrorThrowable.from(e); + } + }); } - /** - * Checks if the Terms are accepted. If not, open Dialog. On positive - * feedback, update the User. - * - * @param user - * @param activity - * @param callback - */ - public void askForTermsOfUseAcceptance(final User user, final Activity activity, - final PositiveNegativeCallback callback) { - boolean verified = false; - try { - verified = verifyTermsUseOfVersion(user.getTermsOfUseVersion()); - } catch (ServerException e) { - LOGGER.warn(e.getMessage(), e); - return; - } - if (!verified) { - - final TermsOfUse current; - try { - current = getCurrentTermsOfUse(); - } catch (ServerException e) { - LOGGER.warn("This should never happen!", e); - return; + private Func1> checkTermsOfUseAcceptance(Activity activity) { + LOGGER.info("checkTermsOfUseAcceptance()"); + return new Func1>() { + @Override + public Observable call(TermsOfUse termsOfUse) { + User user = mUserManager.getUser(); + if (user == null) { + throw OnErrorThrowable.from(new NotLoggedInException( + mContext.getString(R.string.trackviews_not_logged_in))); + } + + LOGGER.info(String.format("Retrieved terms of use for user [%s] with terms of" + + " use version [%s]", user.getUsername(), user.getTermsOfUseVersion())); + + boolean hasAccepted = termsOfUse + .getIssuedDate().equals(user.getTermsOfUseVersion()); + + // If the user has accepted, then just return the generic type + if (hasAccepted) { + return Observable.just(termsOfUse); + } + // If the input activity is not null, then create an dialog observable. + else if (activity != null) { + return createTermsOfUseDialogObservable(user, termsOfUse, activity); + } + // Otherwise, throw an exception. + else { + throw OnErrorThrowable.from(new NotAcceptedTermsOfUseException( + "The user has not accepted the terms of use")); + } } + }; + } - DialogUtil.createTermsOfUseDialog(current, - user.getTermsOfUseVersion() == null, new DialogUtil.PositiveNegativeCallback() { + public Observable createTermsOfUseDialogObservable( + User user, TermsOfUse currentTermsOfUse, Activity activity) { + return new ReactiveTermsOfUseDialog(activity, user, currentTermsOfUse) + .asObservable() + .map(new Func1() { + @Override + public TermsOfUse call(TermsOfUse termsOfUse) { + LOGGER.info("TermsOfUseDialog: the user has accepted the ToU."); - @Override - public void negative() { - LOGGER.info("User did not accept the ToU."); - Crouton.makeText(activity, activity.getString(R.string - .terms_of_use_cant_continue), Style.ALERT).show(); - if (callback != null) { - callback.negative(); - } - } + try { + // set the terms of use + user.setTermsOfUseVersion(termsOfUse.getIssuedDate()); + mDAOProvider.getUserDAO().updateUser(user); + mUserManager.setUser(user); - @Override - public void positive() { - userAcceptedTermsOfUse(user, current.getIssuedDate()); - Crouton.makeText(activity, activity.getString(R.string - .terms_of_use_updating_server), Style.INFO).show(); - if (callback != null) { - callback.positive(); - } - } + LOGGER.info("TermsOfUseDialog: User successfully updated"); - }, activity); - } else { - LOGGER.info("User has accpeted ToU in current version."); - } + return termsOfUse; + } catch (DataUpdateFailureException | UnauthorizedException e) { + LOGGER.warn(e.getMessage(), e); + throw OnErrorThrowable.from(e); + } + } + }); } + +// /** +// * Checks if the Terms are accepted. If not, open Dialog. On positive +// * feedback, update the User. +// * +// * @param user +// * @param callback +// */ +// public void askForTermsOfUseAcceptance(final User user, final PositiveNegativeCallback +// callback) { +// boolean verified = false; +// try { +// verified = verifyTermsUseOfVersion(user.getTermsOfUseVersion()); +// } catch (ServerException e) { +// LOGGER.warn(e.getMessage(), e); +// return; +// } +// if (!verified) { +// +// final TermsOfUse current; +// try { +// current = getCurrentTermsOfUse(); +// } catch (ServerException e) { +// LOGGER.warn("This should never happen!", e); +// return; +// } +// +// new MaterialDialog.Builder(mContext) +// .title(R.string.terms_of_use_title) +// .content((user.getTermsOfUseVersion() == null) ? +// R.string.terms_of_use_sorry : +// R.string.terms_of_use_info) +// .onPositive((materialDialog, dialogAction) -> { +// userAcceptedTermsOfUse(user, current.getIssuedDate()); +// Toast.makeText(mContext, R.string.terms_of_use_updating_server, Toast +// .LENGTH_LONG).show(); +// if (callback != null) { +// callback.positive(); +// } +// }) +// .onNegative((materialDialog, dialogAction) -> { +// LOGGER.info("User did not accept the ToU."); +// Toast.makeText(mContext, R.string.terms_of_use_cant_continue, Toast +// .LENGTH_LONG).show(); +// if (callback != null) { +// callback.negative(); +// } +// }) +// .show(); +// } else { +// LOGGER.info("User has accpeted ToU in current version."); +// } +// } + + public TermsOfUse getCurrentTermsOfUse() throws ServerException { if (this.current == null) { mDAOProvider.getTermsOfUseDAO() @@ -238,11 +344,6 @@ public TermsOfUse call(List termsOfUses) { // } // } - private void setList(List termsOfUse) { - LOGGER.info("List of TermsOfUse size: " + termsOfUse.size()); - list = termsOfUse; - } - public void userAcceptedTermsOfUse(final User user, final String issuedDate) { new AsyncTask() { @Override @@ -263,20 +364,5 @@ protected Void doInBackground(Void... params) { }.execute(); } - /** - * verify the user's accepted terms of use version - * against the latest from the server - * - * @param acceptedTermsOfUseVersion the accepted version of the current user - * @return true, if the provided version is the latest - * @throws ServerException if the server did not respond (as expected) - */ - public boolean verifyTermsUseOfVersion( - String acceptedTermsOfUseVersion) throws ServerException { - if (acceptedTermsOfUseVersion == null) - return false; - - return getCurrentTermsOfUse().getIssuedDate().equals(acceptedTermsOfUseVersion); - } } diff --git a/org.envirocar.app/src/org/envirocar/app/handler/TrackDAOHandler.java b/org.envirocar.app/src/org/envirocar/app/handler/TrackDAOHandler.java new file mode 100644 index 000000000..9fa97cfe7 --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/handler/TrackDAOHandler.java @@ -0,0 +1,204 @@ +package org.envirocar.app.handler; + +import android.content.Context; + +import org.envirocar.core.UserManager; +import org.envirocar.core.entity.Track; +import org.envirocar.core.exception.DataRetrievalFailureException; +import org.envirocar.core.exception.DataUpdateFailureException; +import org.envirocar.core.exception.NoMeasurementsException; +import org.envirocar.core.exception.NotConnectedException; +import org.envirocar.core.exception.TrackSerializationException; +import org.envirocar.core.exception.UnauthorizedException; +import org.envirocar.core.injection.InjectApplicationScope; +import org.envirocar.core.logging.Logger; +import org.envirocar.core.util.TrackMetadata; +import org.envirocar.core.util.Util; +import org.envirocar.remote.DAOProvider; +import org.envirocar.storage.EnviroCarDB; + +import java.util.ArrayList; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import rx.Observable; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; +import rx.schedulers.Schedulers; + +/** + * TODO JavaDoc + * + * @author dewall + */ +@Singleton +public class TrackDAOHandler { + private static final Logger LOGGER = Logger.getLogger(TrackDAOHandler.class); + + private final Context context; + private final UserManager userManager; + private final EnviroCarDB enviroCarDB; + private final DAOProvider daoProvider; + + @Inject + public TrackDAOHandler(@InjectApplicationScope Context context, UserManager userManager, + DAOProvider daoProvider, EnviroCarDB enviroCarDB) { + this.context = context; + this.userManager = userManager; + this.enviroCarDB = enviroCarDB; + this.daoProvider = daoProvider; + } + + public Observable deleteLocalTrackObservable(Track track) { + return enviroCarDB.deleteTrackObservable(track); + } + + /** + * Deletes a track and returns true if the track has been successfully deleted. + * + * @param trackID the id of the track to delete. + * @return true if the track has been successfully deleted. + */ + public boolean deleteLocalTrack(Track.TrackId trackID) { + return deleteLocalTrack( + enviroCarDB.getTrack(trackID) + .subscribeOn(Schedulers.io()) + .toBlocking() + .first()); + } + + /** + * Deletes a track and returns true if the track has been successfully deleted. + * + * @param trackRef the reference of the track. + * @return true if the track has been successfully deleted. + */ + public boolean deleteLocalTrack(Track trackRef) { + LOGGER.info(String.format("deleteLocalTrack(id = %s)", trackRef.getTrackID().getId())); + + // Only delete the track if the track is a local track. + if (trackRef.isLocalTrack()) { + LOGGER.info("deleteLocalTrack(...): Track is a local track."); + enviroCarDB.deleteTrack(trackRef); + return true; + } + + LOGGER.warn("deleteLocalTrack(...): track is no local track. No deletion."); + return false; + } + + /** + * Invokes the deletion of a remote track. Once the remote track has been successfully + * deleted, this method also deletes the locally stored reference of that track. + * + * @param trackRef + * @return + * @throws UnauthorizedException + * @throws NotConnectedException + */ + public boolean deleteRemoteTrack(Track trackRef) throws UnauthorizedException, + NotConnectedException { + LOGGER.info(String.format("deleteRemoteTrack(id = %s)", trackRef.getTrackID().getId())); + + // Check whether this track is a remote track. + if (!trackRef.isRemoteTrack()) { + LOGGER.warn("Track reference to upload is no remote track."); + return false; + } + + // Delete the track first remote and then the local reference. + try { + daoProvider.getTrackDAO().deleteTrack(trackRef); + } catch (DataUpdateFailureException e) { + e.printStackTrace(); + } + + enviroCarDB.deleteTrack(trackRef); + + // Successfully deleted the remote track. + LOGGER.info("deleteRemoteTrack(): Successfully deleted the remote track."); + return true; + } + + public boolean deleteAllRemoteTracksLocally() { + LOGGER.info("deleteAllRemoteTracksLocally()"); + enviroCarDB.deleteAllRemoteTracks() + .subscribeOn(Schedulers.io()) + .toBlocking() + .first(); + return true; + } + + public Observable getLocalTrackCount(){ + return enviroCarDB.getAllLocalTracks(true) + .map(tracks -> tracks.size()); + } + + public Observable updateTrackMetadataObservable(Track track) { + return Observable.just(track) + .map(track1 -> new TrackMetadata(Util.getVersionString(context), + userManager.getUser().getTermsOfUseVersion())) + .flatMap(trackMetadata -> updateTrackMetadata(track + .getTrackID(), + trackMetadata)); + } + + public Observable updateTrackMetadata( + Track.TrackId trackId, TrackMetadata trackMetadata) { + return enviroCarDB.getTrack(trackId, true) + .map(track -> { + TrackMetadata result = track.updateMetadata(trackMetadata); + enviroCarDB.updateTrack(track); + return result; + }); + } + + public Observable fetchRemoteTrackObservable(Track remoteTrack) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + subscriber.onNext(fetchRemoteTrack(remoteTrack)); + subscriber.onCompleted(); + } catch (NotConnectedException e) { + throw OnErrorThrowable.from(e); + } catch (DataRetrievalFailureException e) { + throw OnErrorThrowable.from(e); + } catch (UnauthorizedException e) { + throw OnErrorThrowable.from(e); + } + } + }); + } + + public Track fetchRemoteTrack(Track remoteTrack) throws NotConnectedException, + UnauthorizedException, DataRetrievalFailureException { + try { + Track downloadedTrack = daoProvider.getTrackDAO().getTrackById(remoteTrack + .getRemoteID()); + + // Deep copy... TODO improve this. + remoteTrack.setName(downloadedTrack.getName()); + remoteTrack.setDescription(downloadedTrack.getDescription()); + remoteTrack.setMeasurements(new ArrayList<>(downloadedTrack.getMeasurements())); + remoteTrack.setCar(downloadedTrack.getCar()); + remoteTrack.setTrackStatus(downloadedTrack.getTrackStatus()); + remoteTrack.setMetadata(downloadedTrack.getMetadata()); + + remoteTrack.setStartTime(downloadedTrack.getStartTime()); + remoteTrack.setEndTime(downloadedTrack.getEndTime()); + remoteTrack.setDownloadState(Track.DownloadState.DOWNLOADED); + } catch (NoMeasurementsException e) { + e.printStackTrace(); + } + + try { + enviroCarDB.insertTrack(remoteTrack); + } catch (TrackSerializationException e) { + e.printStackTrace(); + } + // mDBAdapter.insertTrack(remoteTrack, true); + return remoteTrack; + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/handler/TrackRecordingHandler.java b/org.envirocar.app/src/org/envirocar/app/handler/TrackRecordingHandler.java new file mode 100644 index 000000000..81be5f480 --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/handler/TrackRecordingHandler.java @@ -0,0 +1,304 @@ +/** + * Copyright (C) 2013 - 2015 the enviroCar community + *

+ * This file is part of the enviroCar app. + *

+ * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + *

+ * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + *

+ * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ +package org.envirocar.app.handler; + +import android.content.Context; + +import com.squareup.otto.Bus; + +import org.envirocar.app.R; +import org.envirocar.core.entity.Car; +import org.envirocar.core.entity.Measurement; +import org.envirocar.core.entity.Track; +import org.envirocar.core.entity.TrackImpl; +import org.envirocar.core.events.TrackFinishedEvent; +import org.envirocar.core.exception.MeasurementSerializationException; +import org.envirocar.core.exception.NoMeasurementsException; +import org.envirocar.core.injection.InjectApplicationScope; +import org.envirocar.core.injection.Injector; +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.events.BluetoothServiceStateChangedEvent; +import org.envirocar.obd.service.BluetoothServiceState; +import org.envirocar.remote.DAOProvider; +import org.envirocar.storage.EnviroCarDB; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.inject.Inject; + +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class TrackRecordingHandler { + private static final Logger LOGGER = Logger.getLogger(TrackRecordingHandler.class); + private static final DateFormat format = SimpleDateFormat.getDateTimeInstance(); + + private static final long DEFAULT_MAX_TIME_BETWEEN_MEASUREMENTS = 1000 * 60 * 15; + private static final double DEFAULT_MAX_DISTANCE_BETWEEN_MEASUREMENTS = 3.0; + + @Inject + @InjectApplicationScope + protected Context mContext; + @Inject + protected Bus mBus; + @Inject + protected EnviroCarDB mEnvirocarDB; + @Inject + protected BluetoothHandler mBluetoothHandler; + @Inject + protected DAOProvider mDAOProvider; + @Inject + protected TrackDAOHandler trackDAOHandler; + @Inject + protected UserHandler mUserManager; + @Inject + protected TermsOfUseManager mTermsOfUseManager; + @Inject + protected CarPreferenceHandler carHander; + + private Track currentTrack; + + /** + * Constructor. + * + * @param context the context of the activity's scope. + */ + public TrackRecordingHandler(Context context) { + // Inject all annotated fields. + ((Injector) context).injectObjects(this); + } + + public Subscription startNewTrack(PublishSubject publishSubject) { + return getActiveTrackReference(true) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + LOGGER.info("onCompleted()"); + } + + @Override + public void onError(Throwable e) { + LOGGER.error(e.getMessage(), e); + } + + @Override + public void onNext(Track track) { + add(publishSubject.doOnUnsubscribe(new Action0() { + @Override + public void call() { + LOGGER.info("doOnUnsubscribe(): finish current track."); + finishCurrentTrack(); + } + }).subscribe(new Subscriber + () { + @Override + public void onStart() { + super.onStart(); + LOGGER.info("Subscribed on Measurement publisher"); + } + + @Override + public void onCompleted() { + LOGGER.info("NewMeasurementSubject onCompleted()"); + currentTrack = track; + finishCurrentTrack(); + } + + @Override + public void onError(Throwable e) { + LOGGER.error(e.getMessage(), e); + currentTrack = track; + finishCurrentTrack(); + } + + @Override + public void onNext(Measurement measurement) { + LOGGER.info("onNextMeasurement()"); + if (isUnsubscribed()) + return; + LOGGER.info("Insert new measurement "); + + // set the track database ID of the current active track + measurement.setTrackId(track.getTrackID()); + track.getMeasurements().add(measurement); + try { + mEnvirocarDB.insertMeasurement(measurement); + } catch (MeasurementSerializationException e) { + LOGGER.error(e.getMessage(), e); + currentTrack = track; + finishCurrentTrack(); + } + } + })); + } + }); + } + + /** + * Returns the most recent track, which is not finished yet. It only returns the track when + * it has not been finished yet, i.e. its last measurement'S position meets the requirements + * for continuing a track. Otherwise, it sets the track to finished and creates a new database + * entry when required. + * + * @param createNew indicates whether it should create a new track reference when no active + * track is available. + * @return an observable returning the active track reference. + */ + private Observable getActiveTrackReference(boolean createNew) { + return Observable.just(currentTrack) + // Is there a current reference? if not, then try to find an instance in the + // enviroCar database. + .flatMap(track -> track == null ? + mEnvirocarDB.getActiveTrackObservable(false) : Observable.just(track)) + .flatMap(validateTrackRef(createNew)) + // Optimize it.... + .map(track -> { + currentTrack = track; + return track; + }); + } + + /** + * This function checks whether the last unfinished track reference is a valid track + * reference, i.e. if its last measurement's spatial position is no to far away from the + * current position and the time difference between now and the last measurement is not to + * large. + * + * @param createNew should create a new measurement when it is not matching the requirements. + * @return a function that validates the requirements. + */ + private Func1> validateTrackRef(boolean createNew) { + return new Func1>() { + @Override + public Observable call(Track track) { + if (track != null && track.getTrackStatus() == Track.TrackStatus.FINISHED) { + try { + // Check whether the last unfinished track reference is too old to be + // considered. + if ((System.currentTimeMillis() - track.getEndTime() < + DEFAULT_MAX_TIME_BETWEEN_MEASUREMENTS / 10)) + return Observable.just(track); + + // TODO: Spatial Filtering... + + // trackreference is too old. Set it to finished. + track.setTrackStatus(Track.TrackStatus.FINISHED); + mEnvirocarDB.updateTrack(track); + track = null; + } catch (NoMeasurementsException e) { + LOGGER.info("Last unfinished track ref does not contain any measurements." + + " Delete the track"); + + // No Measurements in the last track and it cannot be considered as + // active anymore. Therefore, delete the database entry. + trackDAOHandler.deleteLocalTrack(track); + } + } + + + if (track != null) { + return Observable.just(track); + } else { + // if there is no current reference cached or in the database, then create a new + // one and persist it. + return createNew ? createNewDatabaseTrackObservable() : Observable.just(null); + } + } + }; + } + + private Observable createNewDatabaseTrackObservable() { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + String date = format.format(new Date()); + Car car = carHander.getCar(); + + Track track = new TrackImpl(); + track.setCar(car); + track.setName("Track " + date); + track.setDescription(String.format( + mContext.getString(R.string.default_track_description), car + != null ? car.getModel() : "null")); + + subscriber.onNext(track); + } + }).flatMap(track -> mEnvirocarDB.insertTrackObservable(track)); + } + + /** + * Finishes the current track. On the one hand, the background service that handles the + * connection to the Bluetooth device gets closed and the track in the database gets finished. + */ + public void finishCurrentTrack() { + LOGGER.info("finishCurrentTrack()"); + finishCurrentTrackObservable() + .doOnError(throwable -> LOGGER.warn(throwable.getMessage(), throwable)) + .toBlocking() + .first(); + } + + /** + * Finishes the current track. On the one hand, the background service that handles the + * connection to the Bluetooth device gets closed and the track in the database gets finished. + */ + public Observable finishCurrentTrackObservable() { + LOGGER.info("finishCurrentTrackObservable()"); + + // Set the current remoteService state to SERVICE_STOPPING. + mBus.post(new BluetoothServiceStateChangedEvent(BluetoothServiceState.SERVICE_STOPPING)); + + return getActiveTrackReference(false) + .flatMap(track -> { + // Stop the background service. + mBluetoothHandler.stopOBDConnectionService(); + + if (track == null) + return Observable.just(track); + + // Fire a new TrackFinishedEvent on the event bus. + mBus.post(new TrackFinishedEvent(currentTrack)); + track.setTrackStatus(Track.TrackStatus.FINISHED); + + LOGGER.info(String.format("Track with local id [%s] successful " + + "finished.", track.getTrackID())); + currentTrack = null; + + // Depending on the number of measurements inside the track either update the + // database and return the updated reference or delete the database entry. + return (track.getMeasurements().size() <= 1) ? + mEnvirocarDB.deleteTrackObservable(track) : + mEnvirocarDB.updateTrackObservable(track); + }); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/handler/TrackUploadHandler.java b/org.envirocar.app/src/org/envirocar/app/handler/TrackUploadHandler.java new file mode 100644 index 000000000..0b14eee28 --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/handler/TrackUploadHandler.java @@ -0,0 +1,262 @@ +/** + * Copyright (C) 2013 - 2015 the enviroCar community + *

+ * This file is part of the enviroCar app. + *

+ * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + *

+ * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + *

+ * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ +package org.envirocar.app.handler; + +import android.app.Activity; +import android.content.Context; +import android.widget.Toast; + +import com.google.common.base.Preconditions; + +import org.envirocar.app.R; +import org.envirocar.app.exception.NotAcceptedTermsOfUseException; +import org.envirocar.app.exception.NotLoggedInException; +import org.envirocar.app.exception.TrackAlreadyUploadedException; +import org.envirocar.app.services.NotificationHandler; +import org.envirocar.core.entity.Track; +import org.envirocar.core.exception.NoMeasurementsException; +import org.envirocar.core.exception.TrackWithNoValidCarException; +import org.envirocar.core.injection.InjectApplicationScope; +import org.envirocar.core.logging.Logger; +import org.envirocar.core.utils.CarUtils; +import org.envirocar.core.utils.TrackUtils; +import org.envirocar.remote.DAOProvider; +import org.envirocar.storage.EnviroCarDB; + +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import rx.Observable; +import rx.Scheduler; +import rx.Subscriber; +import rx.android.schedulers.AndroidSchedulers; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Func1; + +/** + * Manager that can upload tracks and cars to the server. + * Use the uploadAllTracks function to upload all local tracks. + * Make sure that you specify the dbAdapter when instantiating. + * The default constructor should only be used when there is no + * other way. + */ +@Singleton +public class TrackUploadHandler { + private static Logger logger = Logger.getLogger(TrackUploadHandler.class); + + private final Context mContext; + private final EnviroCarDB mEnviroCarDB; + private final NotificationHandler mNotificationHandler; + private final CarPreferenceHandler mCarManager; + private final DAOProvider mDAOProvider; + private final TrackDAOHandler trackDAOHandler; + private final UserHandler mUserManager; + private final TrackRecordingHandler mTrackRecordingHandler; + private final TermsOfUseManager mTermsOfUseManager; + + private final Scheduler.Worker mainthreadWorker = AndroidSchedulers.mainThread().createWorker(); + + /** + * Normal constructor for this manager. Specify the context and the dbadapter. + * + * @param context the context of the current scope + */ + @Inject + public TrackUploadHandler(@InjectApplicationScope Context context, + EnviroCarDB enviroCarDB, + NotificationHandler notificationHandler, + CarPreferenceHandler carPreferenceHandler, + DAOProvider daoProvider, + TrackDAOHandler trackDAOHandler, + UserHandler userHandler, + TrackRecordingHandler trackRecordingHandler, + TermsOfUseManager termsOfUseManager) { + this.mContext = context; + this.mEnviroCarDB = enviroCarDB; + this.mNotificationHandler = notificationHandler; + this.mCarManager = carPreferenceHandler; + this.mDAOProvider = daoProvider; + this.trackDAOHandler = trackDAOHandler; + this.mUserManager = userHandler; + this.mTrackRecordingHandler = trackRecordingHandler; + this.mTermsOfUseManager = termsOfUseManager; + } + + + public Observable uploadSingleTrack(Track track, Activity activity) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + logger.info("uploadSingleTrack() start uploading."); + subscriber.onStart(); + + // Create a dialog with which the user can accept the terms of use. + subscriber.add(Observable.just(track) + // general validation of the track + .map(validateRequirementsForUpload()) + // Verify wether the TermsOfUSe have been accepted. + // When the TermsOfUse have not been accepted, create an + // Dialog to accept and continue when the user has accepted. + .flatMap(track1 -> + mTermsOfUseManager.verifyTermsOfUse(activity, track1)) + // Continue when the TermsOfUse has been accepted, otherwise + // throw an error + .flatMap(track1 -> track1 != null ? uploadTrack(track) : + Observable.error(new NotAcceptedTermsOfUseException( + "Not accepted TermsOfUse"))) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + subscriber.onError(e); + subscriber.unsubscribe(); + } + + @Override + public void onNext(Track track) { + subscriber.onNext(track); + subscriber.onCompleted(); + } + } + )); + } + }); + } + + public Observable uploadAllTracks() { + return mEnviroCarDB.getAllLocalTracks() + .flatMap(tracks -> uploadMultipleTracks(tracks)); + } + + public Observable uploadMultipleTracks(List tracks) { + Preconditions.checkState(tracks != null && !tracks.isEmpty(), + "Input tracks cannot be null or empty."); + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + subscriber.onStart(); + mNotificationHandler.createNotification("start"); + + subscriber.add(Observable.from(tracks) + .concatMap(track -> uploadTrack(track)) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + logger.error(e.getMessage(), e); + if (e instanceof NoMeasurementsException) { + mainthreadWorker.schedule(() -> Toast.makeText(mContext, + R.string.uploading_track_no_measurements_after_obfuscation_long, + Toast.LENGTH_LONG).show()); + mNotificationHandler.createNotification + (mContext.getString(R.string + .uploading_track_no_measurements_after_obfuscation)); + } else { + subscriber.onError(e); + } + } + + @Override + public void onNext(Track track) { + subscriber.onNext(track); + } + })); + } + }); + } + + private Observable uploadTrack(Track track) { + return Observable.just(track) + // Check whether the user is correctly logged in. + .map(mUserManager.getIsLoggedIn()) + // Update the track metadata. + .flatMap(track1 -> trackDAOHandler.updateTrackMetadataObservable(track1)) + // Assert whether the track has a temporary car. + .flatMap(trackMetadata -> mCarManager.assertTemporaryCar(track.getCar())) + // Set the car reference + .map(car -> { + track.setCar(car); + return track; + }) + // obfuscate the track. + .map(asObfuscatedTrackWhenChecked()) + // Upload the track + .flatMap(obfTrack -> mDAOProvider.getTrackDAO().createTrackObservable(obfTrack)) + // Update the database entry + .flatMap(track1 -> mEnviroCarDB.updateTrackObservable(track1)); + } + + private Func1 validateRequirementsForUpload() { + return new Func1() { + @Override + public Track call(Track track) { + if (!track.isLocalTrack()) { + String infoText = String.format(mContext.getString(R.string + .trackviews_is_already_uploaded), track.getName()); + logger.warn(infoText); + throw OnErrorThrowable.from(new TrackAlreadyUploadedException(infoText)); + } else if (track.getCar() == null) { + String infoText = "Track has no car set. Please delete this track."; + logger.warn(infoText); + throw OnErrorThrowable.from(new TrackWithNoValidCarException(infoText)); + } else if (!CarUtils.isCarUploaded(track.getCar())) { + String infoText = "Cannot upload tracks with no valid remote car."; + logger.warn(infoText); + throw OnErrorThrowable.from(new TrackWithNoValidCarException(infoText)); + } else if (!mUserManager.isLoggedIn()) { + String infoText = mContext.getString(R.string.trackviews_not_logged_in); + logger.info(infoText); + throw OnErrorThrowable.from(new NotLoggedInException(infoText)); + } + return track; + } + }; + } + + private Func1 asObfuscatedTrackWhenChecked() { + return new Func1() { + @Override + public Track call(Track track) { + logger.info("asObfuscatedTrackWhenChecked()"); + if (PreferencesHandler.isObfuscationEnabled(mContext)) { + logger.info(String.format("obfuscation is enabled. Obfuscating track with %s " + + "measurements.", "" + track.getMeasurements().size())); + try { + return TrackUtils.getObfuscatedTrack(track); + } catch (NoMeasurementsException e) { + throw OnErrorThrowable.from(e); + } + } else { + logger.info("obfuscation is disabled."); + return track; + } + } + }; + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/handler/UploadManager.java b/org.envirocar.app/src/org/envirocar/app/handler/UploadManager.java deleted file mode 100644 index 493e8bd58..000000000 --- a/org.envirocar.app/src/org/envirocar/app/handler/UploadManager.java +++ /dev/null @@ -1,311 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.handler; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.preference.PreferenceManager; -import android.widget.Toast; - -import com.google.common.base.Preconditions; - -import org.envirocar.app.R; -import org.envirocar.app.TrackHandler; -import org.envirocar.app.services.NotificationHandler; -import org.envirocar.app.storage.DbAdapter; -import org.envirocar.core.entity.Car; -import org.envirocar.core.entity.Track; -import org.envirocar.core.exception.DataCreationFailureException; -import org.envirocar.core.exception.NoMeasurementsException; -import org.envirocar.core.exception.NotConnectedException; -import org.envirocar.core.exception.ResourceConflictException; -import org.envirocar.core.exception.UnauthorizedException; -import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; -import org.envirocar.core.logging.Logger; -import org.envirocar.core.util.TrackMetadata; -import org.envirocar.core.util.Util; -import org.envirocar.core.utils.TrackUtils; -import org.envirocar.remote.DAOProvider; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; - -import rx.Observable; -import rx.Scheduler; -import rx.Subscriber; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action0; - -/** - * Manager that can upload tracks and cars to the server. - * Use the uploadAllTracks function to upload all local tracks. - * Make sure that you specify the dbAdapter when instantiating. - * The default constructor should only be used when there is no - * other way. - */ -public class UploadManager { - - public static final String NET_ERROR = "net_error"; - public static final String GENERAL_ERROR = "-1"; - - private static Logger logger = Logger.getLogger(UploadManager.class); - private static Map temporaryAlreadyRegisteredCars = new HashMap(); - - @Inject - @InjectApplicationScope - protected Context mContext; - @Inject - protected DbAdapter mDBAdapter; - @Inject - protected NotificationHandler mNotificationHandler; - @Inject - protected CarPreferenceHandler mCarManager; - @Inject - protected DAOProvider mDAOProvider; - @Inject - protected UserHandler mUserManager; - - private final Scheduler.Worker mainthreadWorker = AndroidSchedulers.mainThread().createWorker(); - - /** - * Normal constructor for this manager. Specify the context and the dbadapter. - * - * @param ctx the context of the current scope - */ - public UploadManager(Context ctx) { - ((Injector) ctx).injectObjects(this); - } - - public boolean isObfuscationEnabled() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - return prefs.getBoolean(PreferenceConstants.OBFUSCATE_POSITION, false); - } - - /** - * This methods uploads all local tracks to the server - */ - public void uploadAllTracks(TrackHandler.TrackUploadCallback callback) { - for (Track track : mDBAdapter.getAllLocalTracks()) { - uploadSingleTrack(track, callback); - } - } - - public Observable uploadTracks(final List tracks) { - Preconditions.checkNotNull(tracks, "Input tracks cannot be null"); - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - mNotificationHandler.createNotification("start"); - - for (Track track : tracks) { - track.setMetadata(mDBAdapter.updateTrackMetadata(track.getTrackID(), - new TrackMetadata(Util.getVersionString(mContext), - mUserManager.getUser().getTermsOfUseVersion()))); - - try { - // assert the car of the track for validity. - assertTermporaryCar(track); - - String result = null; - if (isObfuscationEnabled()) { - logger.info("Obfuscation enabled! Calling TrackUtils" + - ".getObfuscatedTrack()"); - Track obfuscatedTrack = TrackUtils.getObfuscatedTrack(track); - if (obfuscatedTrack.getMeasurements().size() == 0) { - throw new NoMeasurementsException("Track has no measurements " + - "after obfuscation."); - } - result = mDAOProvider.getTrackDAO().createTrack(obfuscatedTrack); - } else { - logger.info("Obfuscation not enabled!"); - result = mDAOProvider.getTrackDAO().createTrack(track); - } - - // When successfully updated, then transit the track from local to remote. - mDBAdapter.transitLocalToRemoteTrack(track, result); - - // Inform the subscriber about the successful transition. - subscriber.onNext(track); - } catch (ResourceConflictException e) { - logger.error(e.getMessage(), e); - subscriber.onError(e); - continue; - } catch (NotConnectedException e) { - logger.error(e.getMessage(), e); - subscriber.onError(e); - continue; - } catch (DataCreationFailureException e) { - logger.error(e.getMessage(), e); - subscriber.onError(e); - continue; - } catch (UnauthorizedException e) { - logger.error(e.getMessage(), e); - subscriber.onError(e); - continue; - } catch (NoMeasurementsException e) { - logger.error(e.getMessage(), e); - subscriber.onError(e); - continue; - } - } - - - subscriber.onCompleted(); - } - }); - } - - private void assertTermporaryCar(Track track) throws NotConnectedException, - UnauthorizedException, DataCreationFailureException { - if (hasTemporaryCar(track)) { - if (!temporaryCarAlreadyRegistered(track)) { - registerCarBeforeUpload(track); - } - } - } - - public void uploadSingleTrack(final Track track, final TrackHandler.TrackUploadCallback - callback) { - if (track == null) return; - - new AsyncTask() { - - @Override - protected Void doInBackground(Void... params) { - Thread.currentThread().setName("TrackUploaderTask-" + track.getTrackID()); - callback.onUploadStarted(track); -// mNotificationHandler.createNotification("start"); - - - /* - * inject track metadata - */ - - track.setMetadata(mDBAdapter.updateTrackMetadata(track.getTrackID(), - new TrackMetadata(Util.getVersionString(mContext), - mUserManager.getUser().getTermsOfUseVersion()))); - - try { - if (hasTemporaryCar(track)) { - /* - * perhaps we already did a registration for this temp car. - * the Map is application uptime scope (static). - */ - if (!temporaryCarAlreadyRegistered(track)) { - registerCarBeforeUpload(track); - } - } - - String result = null; - if (isObfuscationEnabled()) { - Track obfuscatedTrack = TrackUtils.getObfuscatedTrack(track); - result = mDAOProvider.getTrackDAO().createTrack(obfuscatedTrack); - } else { - result = mDAOProvider.getTrackDAO().createTrack(track); - } - -// mNotificationHandler.createNotification("success"); - // track.setRemoteID(result); - // dbAdapter.updateTrack(track); - mDBAdapter.transitLocalToRemoteTrack(track, result); - - if (callback != null) { - callback.onSuccessfulUpload(track); - } - } catch (ResourceConflictException | NotConnectedException | DataCreationFailureException - | UnauthorizedException | NoMeasurementsException e) { - logger.error(e.getMessage(), e); - if (track.getMeasurements().size() == 0) { - alertOnObfuscationMeasurements(); - } - else { - mNotificationHandler.createNotification(mContext - .getString(R.string - .general_error_please_report)); - } - } - - return null; - } - - private void alertOnObfuscationMeasurements() { - /* - * obfuscation removed all measurements - */ - - mainthreadWorker.schedule(new Action0() { - @Override - public void call() { - Toast.makeText(mContext, - R.string.uploading_track_no_measurements_after_obfuscation_long, - Toast.LENGTH_LONG).show(); - } - }); - mNotificationHandler.createNotification - (mContext.getString(R.string - .uploading_track_no_measurements_after_obfuscation)); - } - }.execute(); - } - - private void registerCarBeforeUpload(Track track) throws NotConnectedException, - UnauthorizedException, DataCreationFailureException { - Car car = track.getCar(); - String tempId = car.getId(); - String sensorIdFromServer = mDAOProvider.getSensorDAO().createCar(car); - - car.setId(sensorIdFromServer); - - logger.info("Car id tmpTrack: " + track.getCar().getId()); - - mDBAdapter.updateTrack(track); - mDBAdapter.updateCarIdOfTracks(tempId, car.getId()); - - /* - * we need this hack... Track objects - * in memory are not informed through the DB update - */ - temporaryAlreadyRegisteredCars.put(tempId, car.getId()); - if (mCarManager.getCar().getId().equals(tempId)) { - // if (true) { - mCarManager.setCar(car); - } - } - - private boolean hasTemporaryCar(Track track) { - String id = track.getCar().getId(); - return (id != null) && (id.startsWith(Car.TEMPORARY_SENSOR_ID)); - } - - - public boolean temporaryCarAlreadyRegistered(Track track) { - if (temporaryAlreadyRegisteredCars.containsKey(track.getCar().getId())) { - track.getCar().setId(temporaryAlreadyRegisteredCars.get(track.getCar().getId())); - return true; - } - return false; - } - -} diff --git a/org.envirocar.app/src/org/envirocar/app/handler/UserHandler.java b/org.envirocar.app/src/org/envirocar/app/handler/UserHandler.java index bdbaf41fd..ac2d2132d 100644 --- a/org.envirocar.app/src/org/envirocar/app/handler/UserHandler.java +++ b/org.envirocar.app/src/org/envirocar/app/handler/UserHandler.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -25,13 +25,14 @@ import com.squareup.otto.Bus; +import org.envirocar.app.R; +import org.envirocar.app.exception.NotLoggedInException; import org.envirocar.core.UserManager; import org.envirocar.core.entity.User; import org.envirocar.core.entity.UserImpl; import org.envirocar.core.events.NewUserSettingsEvent; import org.envirocar.core.exception.UnauthorizedException; import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; import org.envirocar.remote.DAOProvider; import org.envirocar.remote.gravatar.GravatarUtils; @@ -39,11 +40,20 @@ import java.io.IOException; import javax.inject.Inject; +import javax.inject.Singleton; import rx.Observable; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Func1; import static android.content.Context.MODE_PRIVATE; +/** + * TODO JavaDoc + * + * @author dewall + */ +@Singleton public class UserHandler implements UserManager { private static final Logger LOG = Logger.getLogger(UserHandler.class); @@ -54,15 +64,9 @@ public class UserHandler implements UserManager { private static final String USER_PREFERENCES = "userPrefs"; - @Inject - protected Bus mBus; - - @Inject - @InjectApplicationScope - protected Context context; - - @Inject - protected DAOProvider mDAOProvider; + private final Context context; + private final Bus bus; + private final DAOProvider daoProvider; private User mUser; private Bitmap mGravatarBitmap; @@ -72,9 +76,11 @@ public class UserHandler implements UserManager { * * @param context the context of the current scope. */ - public UserHandler(Context context) { - // Inject ourselves. - ((Injector) context).injectObjects(this); + @Inject + public UserHandler(@InjectApplicationScope Context context, Bus bus, DAOProvider daoProvider) { + this.context = context; + this.bus = bus; + this.daoProvider = daoProvider; } /** @@ -113,7 +119,7 @@ public void setUser(User user) { // Set the local user reference to the current user. mUser = user; - mBus.post(new NewUserSettingsEvent(user, true)); + bus.post(new NewUserSettingsEvent(user, true)); } /** @@ -132,6 +138,19 @@ public boolean isLoggedIn() { } } + public Func1 getIsLoggedIn() { + return new Func1() { + @Override + public T call(T t) { + if (isLoggedIn()) + return t; + else + throw OnErrorThrowable.from(new NotLoggedInException(context.getString(R + .string.trackviews_not_logged_in))); + } + }; + } + /** * Logs out the user. */ @@ -159,11 +178,11 @@ private void logOut(boolean withoutEvent) { mGravatarBitmap = null; // Delete all local representations of tracks that are already uploaded. - // mTrackHandler.deleteAllRemoteTracksLocally(); + // mTrackRecordingHandler.deleteAllRemoteTracksLocally(); // Fire a new event on the event bus holding indicating that no logged in user exist. if (!withoutEvent) { - mBus.post(new NewUserSettingsEvent(null, false)); + bus.post(new NewUserSettingsEvent(null, false)); } } @@ -180,7 +199,7 @@ public void logIn(String user, String token, LoginCallback callback) { } try { - User result = mDAOProvider.getUserDAO().getUser(user); + User result = daoProvider.getUserDAO().getUser(user); result.setToken(token); setUser(result); diff --git a/org.envirocar.app/src/org/envirocar/app/services/OBDConnectionHandler.java b/org.envirocar.app/src/org/envirocar/app/services/OBDConnectionHandler.java new file mode 100644 index 000000000..3192ab87c --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/services/OBDConnectionHandler.java @@ -0,0 +1,185 @@ +package org.envirocar.app.services; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.IntentFilter; +import android.os.Parcelable; + +import org.envirocar.app.exception.NoOBDSocketConnectedException; +import org.envirocar.app.exception.UUIDSanityCheckFailedException; +import org.envirocar.core.logging.Logger; +import org.envirocar.core.utils.BroadcastUtils; +import org.envirocar.obd.bluetooth.BluetoothSocketWrapper; +import org.envirocar.obd.bluetooth.FallbackBluetoothSocket; +import org.envirocar.obd.bluetooth.NativeBluetoothSocket; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import rx.Observable; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class OBDConnectionHandler { + private static final Logger LOG = Logger.getLogger(OBDConnectionHandler.class); + private static final UUID EMBEDDED_BOARD_SPP = UUID + .fromString("00001101-0000-1000-8000-00805F9B34FB"); + + private final Context context; + + /** + * Constructor + * + * @param context The context of the current scope. + */ + public OBDConnectionHandler(Context context) { + this.context = context; + } + + /** + * @param device the device to start a connection to. + */ + public Observable getOBDConnectionObservable( + final BluetoothDevice device) { + return Observable.just(device) + .map(bluetoothDevice -> { + if (bluetoothDevice.fetchUuidsWithSdp()) + return bluetoothDevice; + else + throw OnErrorThrowable.from(new UUIDSanityCheckFailedException()); + }) + .concatMap(bluetoothDevice -> getUUIDList(bluetoothDevice)) + .concatMap(uuids -> createOBDBluetoothObservable(device, uuids)); + } + + public void shutdownSocket(BluetoothSocketWrapper socket) { + LOG.info("Shutting down bluetooth socket."); + + try { + if (socket.getInputStream() != null) + socket.getInputStream().close(); + if (socket.getOutputStream() != null) + socket.getOutputStream().close(); + socket.close(); + } catch (Exception e) { + LOG.severe(e.getMessage(), e); + } + } + + /** + * @param device + * @return + */ + private Observable> getUUIDList(final BluetoothDevice device) { + LOG.info(String.format("getUUIDList(%s)", device.getName())); + + return BroadcastUtils.createBroadcastObservable(context, + new IntentFilter(BluetoothDevice.ACTION_UUID)) + .first() + .map(intent -> { + LOG.info("getUUIDList(): map call"); + + // Get the device and the UUID provided by the incoming intent. + BluetoothDevice deviceExtra = intent + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Parcelable[] uuidExtra = intent + .getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); + + // If the received broadcast does not belong to this receiver, + // skip it. + if (!deviceExtra.getAddress().equals(device.getAddress())) + return null; + + // Result list to return + List res = new ArrayList(); + + LOG.info(String.format("Adding default UUID: %s", EMBEDDED_BOARD_SPP)); + res.add(EMBEDDED_BOARD_SPP); + + // Create a uuid for every string and return it + for (Parcelable uuid : uuidExtra) { + UUID next = UUID.fromString(uuid.toString()); + if (!res.contains(next)) { + res.add(next); + } + } + + // return the result list + return res; + }); + } + + private Observable createOBDBluetoothObservable( + BluetoothDevice device, List uuids) { + return Observable.create(new Observable.OnSubscribe() { + + private BluetoothSocketWrapper socketWrapper; + + @Override + public void call(Subscriber subscriber) { + for (UUID uuid : uuids) { + // Stop if the subscriber is unsubscribed. + if (subscriber.isUnsubscribed()) + return; + + try { + LOG.info("Trying to create native bleutooth socket"); + socketWrapper = new NativeBluetoothSocket(device + .createRfcommSocketToServiceRecord(uuid)); + } catch (IOException e) { + LOG.warn(e.getMessage(), e); + continue; + } + + try { + connectSocket(); + } catch (FallbackBluetoothSocket.FallbackException | + InterruptedException | + IOException e) { + LOG.warn(e.getMessage(), e); + shutdownSocket(socketWrapper); + socketWrapper = null; + } + + if (socketWrapper != null) { + LOG.info("successful connected"); + subscriber.onNext(socketWrapper); + socketWrapper = null; + subscriber.onCompleted(); + return; + } + } + + if (socketWrapper == null) { + subscriber.onError(new NoOBDSocketConnectedException()); + } + } + + private void connectSocket() throws FallbackBluetoothSocket.FallbackException, + InterruptedException, IOException { + try { + // This is a blocking call and will only return on a + // successful connection or an exception + socketWrapper.connect(); + } catch (IOException e) { + LOG.warn("Exception on bluetooth connection. Trying the fallback... : " + + e.getMessage(), e); + + //try the fallback + socketWrapper = new FallbackBluetoothSocket( + socketWrapper.getUnderlyingSocket()); + Thread.sleep(500); + socketWrapper.connect(); + } + } + + }); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/services/OBDConnectionService.java b/org.envirocar.app/src/org/envirocar/app/services/OBDConnectionService.java index 8963b3579..68c3de812 100644 --- a/org.envirocar.app/src/org/envirocar/app/services/OBDConnectionService.java +++ b/org.envirocar.app/src/org/envirocar/app/services/OBDConnectionService.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -20,87 +20,94 @@ import android.app.Notification; import android.app.NotificationManager; -import android.app.Service; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.os.Binder; -import android.os.IBinder; -import android.os.Parcelable; import android.os.PowerManager; import android.speech.tts.TextToSpeech; import android.support.v4.app.NotificationCompat; -import android.util.Log; -import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; -import org.envirocar.app.CommandListener; +import org.envirocar.algorithm.MeasurementProvider; import org.envirocar.app.R; import org.envirocar.app.events.TrackDetailsProvider; import org.envirocar.app.handler.BluetoothHandler; +import org.envirocar.app.handler.CarPreferenceHandler; import org.envirocar.app.handler.LocationHandler; import org.envirocar.app.handler.PreferencesHandler; +import org.envirocar.app.handler.TrackRecordingHandler; +import org.envirocar.core.entity.Car; +import org.envirocar.core.entity.Measurement; +import org.envirocar.core.events.NewMeasurementEvent; import org.envirocar.core.events.gps.GpsLocationChangedEvent; import org.envirocar.core.events.gps.GpsSatelliteFix; import org.envirocar.core.events.gps.GpsSatelliteFixEvent; -import org.envirocar.core.injection.Injector; +import org.envirocar.core.exception.FuelConsumptionException; +import org.envirocar.core.exception.NoMeasurementsException; +import org.envirocar.core.exception.UnsupportedFuelTypeException; +import org.envirocar.core.injection.BaseInjectorService; import org.envirocar.core.logging.Logger; -import org.envirocar.core.utils.BroadcastUtils; +import org.envirocar.core.trackprocessing.AbstractCalculatedMAFAlgorithm; +import org.envirocar.core.trackprocessing.CalculatedMAFWithStaticVolumetricEfficiency; +import org.envirocar.core.trackprocessing.ConsumptionAlgorithm; +import org.envirocar.core.utils.CarUtils; +import org.envirocar.obd.ConnectionListener; +import org.envirocar.obd.OBDController; import org.envirocar.obd.bluetooth.BluetoothSocketWrapper; -import org.envirocar.obd.bluetooth.FallbackBluetoothSocket; -import org.envirocar.obd.bluetooth.NativeBluetoothSocket; import org.envirocar.obd.events.BluetoothServiceStateChangedEvent; import org.envirocar.obd.events.SpeedUpdateEvent; -import org.envirocar.obd.protocol.ConnectionListener; -import org.envirocar.obd.protocol.OBDCommandLooper; import org.envirocar.obd.service.BluetoothServiceState; +import org.envirocar.storage.EnviroCarDB; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; -import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.inject.Inject; -import rx.Observable; import rx.Scheduler; import rx.Subscriber; import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action0; import rx.schedulers.Schedulers; -import rx.subscriptions.CompositeSubscription; +import rx.subjects.PublishSubject; /** * @author dewall */ -public class OBDConnectionService extends Service { +public class OBDConnectionService extends BaseInjectorService { private static final Logger LOG = Logger.getLogger(OBDConnectionService.class); protected static final int MAX_RECONNECT_COUNT = 2; public static final int BG_NOTIFICATION_ID = 42; - private static final UUID EMBEDDED_BOARD_SPP = UUID - .fromString("00001101-0000-1000-8000-00805F9B34FB"); - public static BluetoothServiceState CURRENT_SERVICE_STATE = BluetoothServiceState .SERVICE_STOPPED; // Injected fields. @Inject - protected Bus mBus; - @Inject protected BluetoothHandler mBluetoothHandler; @Inject protected LocationHandler mLocationHandler; @Inject protected TrackDetailsProvider mTrackDetailsProvider; + @Inject + protected PowerManager.WakeLock mWakeLock; + @Inject + protected MeasurementProvider measurementProvider; + @Inject + protected CarPreferenceHandler carHandler; + @Inject + protected EnviroCarDB enviroCarDB; + @Inject + protected TrackRecordingHandler trackRecordingHandler; + @Inject + protected OBDConnectionHandler obdConnectionHandler; + + private AbstractCalculatedMAFAlgorithm mafAlgorithm; // Text to speech variables. private TextToSpeech mTTS; @@ -108,36 +115,33 @@ public class OBDConnectionService extends Service { private boolean mIsTTSPrefChecked; // Member fields required for the connection to the OBD device. - private CommandListener mCommandListener; - private OBDCommandLooper mOBDCommandLooper; - private OBDBluetoothConnection mOBDConnection; + private OBDController mOBDController; // Different subscriptions - private CompositeSubscription subscriptions = new CompositeSubscription(); - private Subscription mUUIDSubscription; + private Subscription mTTSPrefSubscription; + private Subscription mConnectingSubscription; + private Subscription mMeasurementSubscription; + private BluetoothSocketWrapper bluetoothSocketWrapper; // This satellite fix indicates that there is no satellite connection yet. private GpsSatelliteFix mCurrentGpsSatelliteFix = new GpsSatelliteFix(0, false); - private BluetoothServiceState mServiceState = BluetoothServiceState.SERVICE_STOPPED; - - private IBinder mBinder = new OBDConnectionBinder(); - - private PowerManager.WakeLock mWakeLock; private OBDConnectionRecognizer connectionRecognizer = new OBDConnectionRecognizer(); + private ConsumptionAlgorithm consumptionAlgorithm; + + private final Scheduler.Worker backgroundWorker = Schedulers.io().createWorker(); @Override public void onCreate() { + LOG.info("OBDConnectionService.onCreate()"); super.onCreate(); - // Inject ourselves. - ((Injector) getApplicationContext()).injectObjects(this); - // register on the event bus - this.mBus.register(this); - this.mBus.register(mTrackDetailsProvider); - this.mBus.register(connectionRecognizer); + this.bus.register(this); + this.bus.register(mTrackDetailsProvider); + this.bus.register(connectionRecognizer); + this.bus.register(measurementProvider); mTTS = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() { @Override @@ -151,34 +155,38 @@ public void onInit(int status) { } }); - subscriptions.add( + mTTSPrefSubscription = PreferencesHandler.getTextToSpeechObservable(getApplicationContext()) .subscribe(aBoolean -> { mIsTTSPrefChecked = aBoolean; - })); + }); + + /** + * create the consumption and MAF algorithm, final for this connection + */ + Car car = carHandler.getCar(); + this.consumptionAlgorithm = CarUtils.resolveConsumptionAlgorithm(car.getFuelType()); + + this.mafAlgorithm = new CalculatedMAFWithStaticVolumetricEfficiency(car); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - // Start the location - mLocationHandler.startLocating(); + LOG.info("OBDConnectionService.onStartCommand()"); + doTextToSpeech("Establishing connection"); // Acquire the wake lock for keeping the CPU active. - PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "wakelock"); mWakeLock.acquire(); - - // - doTextToSpeech("Establishing connection"); + // Start the location + mLocationHandler.startLocating(); // Get the default device BluetoothDevice device = mBluetoothHandler.getSelectedBluetoothDevice(); - if (device != null) { - LOG.info("Start the OBD connection"); + LOG.info("The BluetoothHandler has a valid device. Start the OBD connection"); // Start the OBD Connection. - startOBDConnection(device); + mConnectingSubscription = startOBDConnection(device); } else { LOG.severe("No default Bluetooth device selected"); } @@ -193,49 +201,39 @@ public int onStartCommand(Intent intent, int flags, int startId) { */ private void setBluetoothServiceState(BluetoothServiceState state) { // Set the new remoteService state - this.mServiceState = state; CURRENT_SERVICE_STATE = state; // TODO FIX // and fire an event on the event bus. - this.mBus.post(produceBluetoothServiceStateChangedEvent()); + this.bus.post(produceBluetoothServiceStateChangedEvent()); } // @Produce public BluetoothServiceStateChangedEvent produceBluetoothServiceStateChangedEvent() { LOG.info(String.format("produceBluetoothServiceStateChangedEvent(): %s", - mServiceState.toString())); - return new BluetoothServiceStateChangedEvent(mServiceState); - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; + CURRENT_SERVICE_STATE.toString())); + return new BluetoothServiceStateChangedEvent(CURRENT_SERVICE_STATE); } @Override public void onDestroy() { - LOG.info("onDestroy()"); + LOG.info("OBDConnectionService.onDestroy()"); super.onDestroy(); - if (subscriptions != null) - subscriptions.unsubscribe(); - - // If there is an active UUID subscription. - if (mUUIDSubscription != null) - mUUIDSubscription.unsubscribe(); - // Stop this remoteService and emove this remoteService from foreground state. stopOBDConnection(); - stopForeground(true); - - if (mWakeLock != null) - mWakeLock.release(); // Unregister from the event bus. - mBus.unregister(this); - mBus.unregister(mTrackDetailsProvider); - mBus.unregister(connectionRecognizer); - mTrackDetailsProvider.onOBDConnectionStopped(); + bus.unregister(this); + bus.unregister(mTrackDetailsProvider); + bus.unregister(connectionRecognizer); + bus.unregister(measurementProvider); + + LOG.info("OBDConnectionService successfully destroyed"); + } + + @Override + public List getInjectionModules() { + return Arrays.asList(new OBDServiceModule()); } @Subscribe @@ -253,142 +251,59 @@ public void onReceiveGpsSatelliteFixEvent(GpsSatelliteFixEvent event) { private void doTextToSpeech(String string) { if (mIsTTSAvailable && mIsTTSPrefChecked) { - mTTS.speak("enviro car ".concat(string), TextToSpeech.QUEUE_ADD, null); + mTTS.speak(string, TextToSpeech.QUEUE_ADD, null); } } - /** - * @param uuids - * @return - */ - private Observable transformUUID(final Parcelable uuids[]) { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - // Create a uuid for every string and return it - for (Parcelable uuid : uuids) { - subscriber.onNext(UUID.fromString(uuid.toString())); - } - subscriber.onCompleted(); - } - }); - } - - - /** - * @param device - * @return - */ - private Observable> getUUIDList(final BluetoothDevice device) { - LOG.info(String.format("getUUIDList(%s)", device.getName())); - - return BroadcastUtils.createBroadcastObservable(getApplicationContext(), - new IntentFilter(BluetoothDevice.ACTION_UUID)) - .map(intent -> { - LOG.info("getUUIDList(): map call"); - - // Get the device and the UUID provided by the incoming intent. - BluetoothDevice deviceExtra = intent - .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - Parcelable[] uuidExtra = intent - .getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); - - // If the received broadcast does not belong to this receiver, - // skip it. - if (!deviceExtra.getAddress().equals(device.getAddress())) - return null; - - // Result list to return - List res = new ArrayList(); - - LOG.info(String.format("Adding default UUID: %s", EMBEDDED_BOARD_SPP)); - res.add(EMBEDDED_BOARD_SPP); - - // Create a uuid for every string and return it - for (Parcelable uuid : uuidExtra) { - UUID next = UUID.fromString(uuid.toString()); - if (!res.contains(next)) { - res.add(next); - } - } - - // return the result list - return res; - }); - } - /** * @param device the device to start a connection to. */ - private void startOBDConnection(final BluetoothDevice device) { - LOG.info("startOBDConnection"); - - // Set remoteService state to STARTING and fire an event on the bus. - setBluetoothServiceState(BluetoothServiceState.SERVICE_STARTING); - - if (device.fetchUuidsWithSdp()) - mUUIDSubscription = getUUIDList(device) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(uuids -> { - Log.i("yea", "start bluetooth connection thread"); - (mOBDConnection = new OBDBluetoothConnection(device, uuids)).start(); - mUUIDSubscription.unsubscribe(); - }); - } - - /** - * Method that stops the remoteService, removes everything from the waiting list - */ - private void stopOBDConnection() { - LOG.info("stopOBDConnection called"); - new Thread(() -> { - shutdownConnectionAndHandler(); - - setBluetoothServiceState(BluetoothServiceState.SERVICE_STOPPED); - - mLocationHandler.stopLocating(); - - if (mCommandListener != null) { - mCommandListener.shutdown(); - } - - Notification noti = new NotificationCompat.Builder(getApplicationContext()) - .setContentTitle("enviroCar") - .setContentText(getResources() - .getText(R.string.service_state_stopped)) - .setSmallIcon(R.drawable.dashboard).setAutoCancel(true).build(); + private Subscription startOBDConnection(final BluetoothDevice device) { + return obdConnectionHandler.getOBDConnectionObservable(device) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe(new Subscriber() { + @Override + public void onStart() { + LOG.info("onStart() connection"); + + // Set remoteService state to STARTING and fire an event on the bus. + setBluetoothServiceState(BluetoothServiceState.SERVICE_STARTING); + } - NotificationManager manager = (NotificationManager) getSystemService(Context - .NOTIFICATION_SERVICE); - manager.notify(BG_NOTIFICATION_ID, noti); + @Override + public void onCompleted() { + LOG.info("onCompleted(): BluetoothSocketWrapper connection completed"); + } - doTextToSpeech("Device disconnected"); + @Override + public void onError(Throwable e) { + LOG.error(e.getMessage(), e); + unsubscribe(); + } - // Set state of the remoteService to stopped. - setBluetoothServiceState(BluetoothServiceState.SERVICE_STOPPED); - }).start(); + @Override + public void onNext(BluetoothSocketWrapper socketWrapper) { + LOG.info("startOBDConnection.onNext() socket successfully connected."); + bluetoothSocketWrapper = socketWrapper; + onDeviceConnected(bluetoothSocketWrapper); + onCompleted(); + } + }); } private void onDeviceConnected(BluetoothSocketWrapper bluetoothSocket) { + LOG.info(String.format("OBDConnectionService.onDeviceConntected(%s)", + bluetoothSocket.getRemoteDeviceName())); try { - InputStream in = bluetoothSocket.getInputStream(); - OutputStream out = bluetoothSocket.getOutputStream(); - - if (mCommandListener != null) { - mCommandListener.shutdown(); - } - - this.mCommandListener = new CommandListener(getApplicationContext()); - this.mOBDCommandLooper = new OBDCommandLooper(in, out, bluetoothSocket - .getRemoteDeviceName(), this.mCommandListener, new ConnectionListener() { - + this.mOBDController = new OBDController(bluetoothSocket, new ConnectionListener() { private int mReconnectCount = 0; @Override public void onConnectionVerified() { setBluetoothServiceState(BluetoothServiceState.SERVICE_STARTED); + subscribeForMeasurements(); } @Override @@ -396,8 +311,6 @@ public void onAllAdaptersFailed() { LOG.info("all adapters failed!"); stopOBDConnection(); doTextToSpeech("failed to connect to the OBD adapter"); - // sendBroadcast(new Intent - // (CONNECTION_PERMANENTLY_FAILED_INTENT)); } @Override @@ -414,147 +327,135 @@ public void requestConnectionRetry(IOException e) { doTextToSpeech("Connection lost. Trying to reconnect."); } } - }); - this.mOBDCommandLooper.start(); + }, bus); } catch (IOException e) { LOG.warn(e.getMessage(), e); - deviceDisconnected(); + stopSelf(); return; } doTextToSpeech("Connection established"); } - public void deviceDisconnected() { - LOG.info("Bluetooth device disconnected."); - stopOBDConnection(); - } - - private void shutdownConnectionAndHandler() { - if (mOBDCommandLooper != null) { - mOBDCommandLooper.stopLooper(); - } - - if (mOBDConnection != null) { - mOBDConnection.cancelConnection(); - } - } - /** - * + * Method that stops the remoteService, removes everything from the waiting list */ - class OBDBluetoothConnection extends Thread { + private void stopOBDConnection() { + LOG.info("stopOBDConnection called"); + backgroundWorker.schedule(() -> { + stopForeground(true); + + // If there is an active UUID subscription. + if (mConnectingSubscription != null && !mConnectingSubscription.isUnsubscribed()) + mConnectingSubscription.unsubscribe(); + if (mTTSPrefSubscription != null && !mTTSPrefSubscription.isUnsubscribed()) + mTTSPrefSubscription.unsubscribe(); + if (mMeasurementSubscription != null && !mMeasurementSubscription.isUnsubscribed()) + mMeasurementSubscription.unsubscribe(); + + if (mOBDController != null) + mOBDController.shutdown(); + if (bluetoothSocketWrapper != null) + bluetoothSocketWrapper.shutdown(); + if (connectionRecognizer != null) + connectionRecognizer.shutDown(); + if (mTrackDetailsProvider != null) + mTrackDetailsProvider.clear(); + if (mWakeLock != null && mWakeLock.isHeld()) { + mWakeLock.release(); + } - // The required input variables. - private final BluetoothDevice mDevice; - private final List mUUIDCandidates; + mLocationHandler.stopLocating(); + showServiceStateStoppedNotification(); + doTextToSpeech("Device disconnected"); - // Boolean variables indicating the state. - private boolean mSuccess; - private boolean mIsRunning; + // Set state of the remoteService to stopped. + setBluetoothServiceState(BluetoothServiceState.SERVICE_STOPPED); + }); + } - // The socket wrapper for the connection. - private BluetoothSocketWrapper mSocketWrapper; - /** - * Constructor - * - * @param device - * @param uuids - */ - public OBDBluetoothConnection(BluetoothDevice device, List uuids) { - this.mDevice = device; - this.mUUIDCandidates = uuids; - this.mIsRunning = true; - } + private void subscribeForMeasurements() { + // this is the first access to the measurement objects push it further + Long samplingRate = PreferencesHandler.getSamplingRate(getApplicationContext()) * 1000; + mMeasurementSubscription = measurementProvider.measurements(samplingRate) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe(getMeasurementSubscriber()); + } - @Override - public void run() { - for (UUID uuid : mUUIDCandidates) { - if (!mIsRunning) - return; + private Subscriber getMeasurementSubscriber() { + return new Subscriber() { + PublishSubject measurementPublisher = + PublishSubject.create(); - try { - LOG.info("Trying to create native bleutooth socket"); - mSocketWrapper = new NativeBluetoothSocket(mDevice - .createRfcommSocketToServiceRecord(uuid)); - } catch (IOException e) { - LOG.info("Error"); - - LOG.warn(e.getMessage(), e); - continue; - } + @Override + public void onStart() { + LOG.info("onStart(): MeasuremnetProvider Subscription"); + add(trackRecordingHandler.startNewTrack(measurementPublisher)); + } - if (mSocketWrapper == null) - continue; + @Override + public void onCompleted() { + LOG.info("onCompleted(): MeasurementProvider"); + measurementPublisher.onCompleted(); + measurementPublisher = null; + } + + @Override + public void onError(Throwable e) { + LOG.error(e.getMessage(), e); + measurementPublisher.onError(e); + measurementPublisher = null; + } + @Override + public void onNext(Measurement measurement) { + LOG.info("onNNNNENEEXT()"); try { - // This is a blocking call and will only return on a - // successful connection or an exception - mSocketWrapper.connect(); - mSuccess = true; - } catch (IOException e) { - LOG.warn("Exception on bluetooth connection. Trying " + - "the fallback... : " - + e.getMessage(), e); - try { - //try the fallback - if (mIsRunning) { - - mSocketWrapper = new FallbackBluetoothSocket(mSocketWrapper - .getUnderlyingSocket()); - Thread.sleep(500); - mSocketWrapper.connect(); - mSuccess = true; + if (!measurement.hasProperty(Measurement.PropertyKey.MAF)) { + try { + measurement.setProperty(Measurement.PropertyKey + .CALCULATED_MAF, mafAlgorithm.calculateMAF(measurement)); + } catch (NoMeasurementsException e) { + LOG.warn(e.getMessage()); } - } catch (FallbackBluetoothSocket.FallbackException e1) { - e1.printStackTrace(); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } catch (IOException e1) { - shutdownSocket(mSocketWrapper); } - } - if (mSuccess) { - LOG.info("successful connected"); - onDeviceConnected(mSocketWrapper); - break; + if (consumptionAlgorithm != null) { + double consumption = consumptionAlgorithm.calculateConsumption(measurement); + double co2 = consumptionAlgorithm.calculateCO2FromConsumption(consumption); + measurement.setProperty(Measurement.PropertyKey.CONSUMPTION, consumption); + measurement.setProperty(Measurement.PropertyKey.CO2, co2); + } + } catch (FuelConsumptionException e) { + LOG.warn(e.getMessage()); + } catch (UnsupportedFuelTypeException e) { + LOG.warn(e.getMessage()); } - } - } - - private void shutdownSocket(BluetoothSocketWrapper socket) { - OBDConnectionService.LOG.info("Shutting down bluetooth socket."); - - try { - if (socket.getInputStream() != null) - socket.getInputStream().close(); - - } catch (Exception e) { - } - - try { - if (socket.getOutputStream() != null) - socket.getOutputStream().close(); - } catch (Exception e) { + measurementPublisher.onNext(measurement); + bus.post(new NewMeasurementEvent(measurement)); } + }; + } - try { - socket.close(); - } catch (Exception e) { - } - } - private void cancelConnection() { - mIsRunning = false; - shutdownSocket(mSocketWrapper); - } + private void showServiceStateStoppedNotification() { + NotificationManager manager = (NotificationManager) getSystemService(Context + .NOTIFICATION_SERVICE); + Notification noti = new NotificationCompat.Builder(getApplicationContext()) + .setContentTitle("enviroCar") + .setContentText(getResources() + .getText(R.string.service_state_stopped)) + .setSmallIcon(R.drawable.dashboard) + .setAutoCancel(true) + .build(); + manager.notify(BG_NOTIFICATION_ID, noti); } - public class OBDConnectionRecognizer { + private final class OBDConnectionRecognizer { private static final long OBD_INTERVAL = 1000 * 10; // 10 seconds; private static final long GPS_INTERVAL = 1000 * 60 * 2; // 2 minutes; @@ -567,21 +468,14 @@ public class OBDConnectionRecognizer { private final Action0 gpsConnectionCloser = () -> { LOG.warn("CONNECTION CLOSED due to no GPS values"); - stopOBDConnection(); + stopSelf(); }; private final Action0 obdConnectionCloser = () -> { LOG.warn("CONNECTION CLOSED due to no OBD values"); - stopOBDConnection(); + stopSelf(); }; - /** - * Constructor. - */ - public OBDConnectionRecognizer() { - - } - @Subscribe public void onReceiveGpsLocationChangedEvent(GpsLocationChangedEvent event) { if (mGPSCheckerSubscription != null) { @@ -597,6 +491,7 @@ public void onReceiveGpsLocationChangedEvent(GpsLocationChangedEvent event) { @Subscribe public void onReceiveSpeedUpdateEvent(SpeedUpdateEvent event) { + LOG.info("Received speed update, no stop required via mOBDCheckerSubscription!"); if (mOBDCheckerSubscription != null) { mOBDCheckerSubscription.unsubscribe(); mOBDCheckerSubscription = null; @@ -607,22 +502,13 @@ public void onReceiveSpeedUpdateEvent(SpeedUpdateEvent event) { mOBDCheckerSubscription = mBackgroundWorker.schedule( obdConnectionCloser, OBD_INTERVAL, TimeUnit.MILLISECONDS); } - } - - /** - * Class used for the client Binder. The remoteService is running in the same process as its - * client, so it is not required to deal with IPC. - */ - public class OBDConnectionBinder extends Binder { - - /** - * Returns the instance of the enclosing remoteService. - * - * @return the enclosing remoteService. - */ - public OBDConnectionService getService() { - return OBDConnectionService.this; + public void shutDown() { + LOG.info("shutDown() OBDConnectionRecognizer"); + if (mOBDCheckerSubscription != null) + mOBDCheckerSubscription.unsubscribe(); + if (mGPSCheckerSubscription != null) + mGPSCheckerSubscription.unsubscribe(); } } } diff --git a/org.envirocar.app/src/org/envirocar/app/services/OBDServiceModule.java b/org.envirocar.app/src/org/envirocar/app/services/OBDServiceModule.java index bd7c1d1a3..f7ea63a91 100644 --- a/org.envirocar.app/src/org/envirocar/app/services/OBDServiceModule.java +++ b/org.envirocar.app/src/org/envirocar/app/services/OBDServiceModule.java @@ -1,26 +1,32 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.app.services; +import android.content.Context; +import android.os.PowerManager; + import com.squareup.otto.Bus; +import org.envirocar.algorithm.InterpolationMeasurementProvider; +import org.envirocar.algorithm.MeasurementProvider; import org.envirocar.app.events.TrackDetailsProvider; +import org.envirocar.core.injection.InjectApplicationScope; import javax.inject.Singleton; @@ -33,19 +39,35 @@ @Module( complete = false, library = true, - injects = {} + injects = { + OBDConnectionService.class, + OBDConnectionHandler.class + } ) public class OBDServiceModule { -// @Singleton -// @Provides -// TextToSpeech provideTextToSpeech(){ -// return new TextToSpeech() -// } + @Singleton + @Provides + PowerManager.WakeLock provideWakeLock(@InjectApplicationScope Context context) { + return ((PowerManager) context.getSystemService(Context.POWER_SERVICE)) + .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "wakelock"); + } @Singleton @Provides - TrackDetailsProvider provideTrackDetails(Bus bus){ + TrackDetailsProvider provideTrackDetails(Bus bus) { return new TrackDetailsProvider(bus); } + + @Singleton + @Provides + MeasurementProvider provideMeasurementProvider() { + return new InterpolationMeasurementProvider(); + } + + @Singleton + @Provides + OBDConnectionHandler provideOBDConnectionHandler(@InjectApplicationScope Context context) { + return new OBDConnectionHandler(context); + } } diff --git a/org.envirocar.app/src/org/envirocar/app/services/SystemStartupService.java b/org.envirocar.app/src/org/envirocar/app/services/SystemStartupService.java index 5c6e27c21..381be7bef 100644 --- a/org.envirocar.app/src/org/envirocar/app/services/SystemStartupService.java +++ b/org.envirocar.app/src/org/envirocar/app/services/SystemStartupService.java @@ -32,7 +32,7 @@ import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; -import org.envirocar.app.TrackHandler; +import org.envirocar.app.handler.TrackRecordingHandler; import org.envirocar.app.handler.BluetoothHandler; import org.envirocar.app.handler.CarPreferenceHandler; import org.envirocar.app.handler.PreferenceConstants; @@ -82,7 +82,7 @@ public class SystemStartupService extends Service { @Inject protected NotificationHandler mNotificationHandler; @Inject - protected TrackHandler mTrackHandler; + protected TrackRecordingHandler mTrackRecordingHandler; @Inject protected CarPreferenceHandler mCarManager; @@ -148,7 +148,7 @@ else if (ACTION_STOP_TRACK_RECORDING.equals(action)) { LOGGER.info("Received Broadcast: Stop Track Recording."); // Finish the current track. - mTrackHandler.finishCurrentTrack(); + mTrackRecordingHandler.finishCurrentTrack(); } } }; diff --git a/org.envirocar.app/src/org/envirocar/app/services/TrackUploadService.java b/org.envirocar.app/src/org/envirocar/app/services/TrackUploadService.java index c4a1c5c90..a7f2ad911 100644 --- a/org.envirocar.app/src/org/envirocar/app/services/TrackUploadService.java +++ b/org.envirocar.app/src/org/envirocar/app/services/TrackUploadService.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -29,12 +29,13 @@ import org.envirocar.app.BaseMainActivity; import org.envirocar.app.R; -import org.envirocar.app.TrackHandler; import org.envirocar.app.exception.NotAcceptedTermsOfUseException; -import org.envirocar.app.storage.DbAdapter; +import org.envirocar.app.handler.TrackRecordingHandler; +import org.envirocar.app.handler.TrackUploadHandler; import org.envirocar.core.entity.Track; import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; +import org.envirocar.storage.EnviroCarDB; import java.util.List; @@ -54,16 +55,18 @@ public class TrackUploadService extends Service { private static final int NOTIFICATION_ID = 52; @Inject - protected TrackHandler trackHandler; + protected TrackRecordingHandler trackRecordingHandler; + @Inject + protected EnviroCarDB enviroCarDB; @Inject - protected DbAdapter dbAdapter; + protected TrackUploadHandler trackUploadHandler; @Override public void onCreate() { LOG.debug("onCreate()"); super.onCreate(); - // Inject the TrackHandler; + // Inject the TrackRecordingHandler; ((Injector) getApplicationContext()).injectObjects(this); } @@ -71,11 +74,13 @@ public void onCreate() { public int onStartCommand(Intent intent, int flags, int startId) { LOG.info("onStartCommand()"); - List localTrackList = dbAdapter.getAllLocalTracks(true); + + // TODO change it to clean u + List localTrackList = enviroCarDB.getAllLocalTracks().first().toBlocking().first(); if (localTrackList.size() > 0) { LOG.info(String.format("%s local tracks to upload", localTrackList.size())); - trackHandler.uploadAllTracks() + trackUploadHandler.uploadAllTracks() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber() { diff --git a/org.envirocar.app/src/org/envirocar/app/storage/DbAdapter.java b/org.envirocar.app/src/org/envirocar/app/storage/DbAdapter.java deleted file mode 100644 index 62a754246..000000000 --- a/org.envirocar.app/src/org/envirocar/app/storage/DbAdapter.java +++ /dev/null @@ -1,275 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.storage; - -import org.envirocar.core.delete.Position; -import org.envirocar.core.entity.Measurement; -import org.envirocar.core.entity.Track; -import org.envirocar.core.exception.MeasurementSerializationException; -import org.envirocar.core.exception.TrackAlreadyFinishedException; -import org.envirocar.core.util.TrackMetadata; - -import java.util.ArrayList; -import java.util.List; - -import rx.Observable; - - -/** - * DB Adapter Interface that saves measurements in a local SQLite Database - * - * @author jakob - * - */ - -public interface DbAdapter { - - /** - * Method to open the DB connection - * - * @return DbAdapter Object that can be used to call the other methods - * @deprecated implementations should take care of that on their own - */ - @Deprecated - public DbAdapter open(); - - /** - * Close the DB connection. Should be called when the app stops - */ - public void close(); - - /** - * Check whether the database is opened at the moment. - * @return true if db is open. - */ - public boolean isOpen(); - - /** - * Inserts a measurements into the database - * - * @param measurement - * The measurement that should be inserted - * @throws TrackAlreadyFinishedException - * @throws MeasurementSerializationException - */ - public void insertNewMeasurement(Measurement measurement) throws TrackAlreadyFinishedException, MeasurementSerializationException; - - /** - * Inserts a track into the database - * - * @param track - * The track that should be inserted - * @return the id of the track that has been inserted - */ - public long insertTrack(Track track); - - /** - * Inserts a track into the database - * - * @param track - * The track that should be inserted - * @param remote - * if the track is a remote (=server, already finished) track - * @return the id of the track that has been inserted - */ - public long insertTrack(Track track, boolean remote); - - /** - * Updates a Track in the database - * - * @param track - * the track to update - * @return true or false if the query was successful - */ - public boolean updateTrack(Track track); - - - /** - * An implementation shall return the current track that - * measurements should be appended to. - * It shall determine if using a non-finalized track - * is reasonable (based on time and space constraints, using - * the system time and the provided currentLocation) - * If there is no non-finalized track or appending is not - * reasonable, a new track shall be created. - * - * @param currentLocation the current location - * @return the current active track as reference via TrackId - */ - public Track.TrackId getActiveTrackReference(Position currentLocation); - - /** - * Returns all tracks as an ArrayList - * - * @return All tracks in an ArrayList - */ - public ArrayList getAllTracks(); - - /** - * @param lazyMeasurements if true, an implementation shall return - * {@link Track} objects that load their measurements in lazy fashion - * @return all tracks - */ - public List getAllTracks(boolean lazyMeasurements); - - - - public Observable getTrackObservable(boolean lazyMeasurements); - - /** - * Returns one track specified by the id - * - * @param id - * The id of the track that should be returned - * @return The desired track or null if it does not exist - */ - public Track getTrack(Track.TrackId id); - - /** - * Returns one track specified by the id - * - * @param id the tracks internal id - * @param lazyMeasurements if true, an implementation shall return a - * {@link Track} that loads its measurements in lazy fashion - * @return the desired track - */ - public Track getTrack(Track.TrackId id, boolean lazyMeasurements); - - /** - * Returns true if a track with the given id is in the Database - * - * @param id - * The id id ot the checked track - * @return exists a track with the id - */ - public boolean hasTrack(Track.TrackId id); - - /** - * Deletes all tracks and measurements in the database - */ - public void deleteAllTracks(); - - /** - * Returns the number of stored tracks in the SQLite database - */ - public int getNumberOfStoredTracks(); - - /** - * Retruns the track that was last inserted into the database - * - * @return the latest track of the DB or null if there are no tracks - */ - public Track getLastUsedTrack(); - - - /** - * @see #getLastUsedTrack() - * - * @param lazyMeasurements if the measurements should be deserialized - * @return see {@link #getLastUsedTrack()} - */ - public Track getLastUsedTrack(boolean lazyMeasurements); - - /** - * Delete track specified by id. - * - * @param id - * id of the track to be deleted. - */ - public void deleteTrack(Track.TrackId id); - - public int getNumberOfRemoteTracks(); - - public int getNumberOfLocalTracks(); - - /** - * an implementation shall delete (!) all - * local tracks from the underlying persistence - * layer. - * - * Friendly warning: every local track (= not yet - * uploaded) will be lost irreversible. - */ - public void deleteAllLocalTracks(); - - /** - * an implementation shall remove - * all local representations of remote tracks from - * the underlying persistence layer. - */ - public void deleteAllRemoteTracks(); - - public List getAllLocalTracks(); - - public List getAllLocalTracks(boolean lazyMeasurements); - - public List getAllRemoteTracks(); - - public List getAllRemoteTracks(boolean lazyMeasurements); - - - public Track createNewTrack(); - - - public Track finishCurrentTrack(); - - /** - * an implementation shall return all meaasurements - * for the given track. - * - * @param track the track object - * @return the list of Measurements - */ - public List getAllMeasurementsForTrack(Track track); - - /** - * an implementation shall update the ID - * of all Track's cars which currently have the currentId - * and update it to newId. - * - * @param currentId - * @param newId - */ - public void updateCarIdOfTracks(String currentId, String newId); - - void insertMeasurement(Measurement measurement) throws TrackAlreadyFinishedException, MeasurementSerializationException; - - void insertMeasurement(Measurement measurement, boolean ignoreFinished) - throws MeasurementSerializationException, TrackAlreadyFinishedException; - - public TrackMetadata updateTrackMetadata(Track.TrackId trackId, TrackMetadata trackMetadata); - - public void transitLocalToRemoteTrack(Track track, String remoteId); - - /** - * use this method to load measurements for a track that - * is marked as lazy loaded. - * - * An implementation shall set the field - * to false after loading and setting the measurements. - * - * @param t the track - */ - public void loadMeasurements(Track t); - - public void setConnectedOBDDevice(TrackMetadata obdDeviceMetadata); - - - -} diff --git a/org.envirocar.app/src/org/envirocar/app/storage/DbAdapterImpl.java b/org.envirocar.app/src/org/envirocar/app/storage/DbAdapterImpl.java deleted file mode 100644 index 274d954f9..000000000 --- a/org.envirocar.app/src/org/envirocar/app/storage/DbAdapterImpl.java +++ /dev/null @@ -1,853 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.storage; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import org.envirocar.app.R; -import org.envirocar.app.handler.CarPreferenceHandler; -import org.envirocar.core.delete.Position; -import org.envirocar.core.entity.Car; -import org.envirocar.core.entity.CarImpl; -import org.envirocar.core.entity.Measurement; -import org.envirocar.core.entity.MeasurementImpl; -import org.envirocar.core.entity.Track; -import org.envirocar.core.entity.TrackImpl; -import org.envirocar.core.exception.MeasurementSerializationException; -import org.envirocar.core.exception.NoMeasurementsException; -import org.envirocar.core.exception.TrackAlreadyFinishedException; -import org.envirocar.core.injection.InjectApplicationScope; -import org.envirocar.core.injection.Injector; -import org.envirocar.core.logging.Logger; -import org.envirocar.core.util.TrackMetadata; -import org.envirocar.core.util.Util; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; - -import rx.Observable; - -public class DbAdapterImpl implements DbAdapter { - - private static final Logger logger = Logger.getLogger(DbAdapterImpl.class); - - public static final String TABLE_MEASUREMENT = "measurements"; - public static final String KEY_MEASUREMENT_TIME = "time"; - public static final String KEY_MEASUREMENT_LONGITUDE = "longitude"; - public static final String KEY_MEASUREMENT_LATITUDE = "latitude"; - public static final String KEY_MEASUREMENT_ROWID = "_id"; - public static final String KEY_MEASUREMENT_PROPERTIES = "properties"; - public static final String KEY_MEASUREMENT_TRACK = "track"; - public static final String[] ALL_MEASUREMENT_KEYS = new String[]{ - KEY_MEASUREMENT_ROWID, - KEY_MEASUREMENT_TIME, - KEY_MEASUREMENT_LONGITUDE, - KEY_MEASUREMENT_LATITUDE, - KEY_MEASUREMENT_PROPERTIES, - KEY_MEASUREMENT_TRACK - }; - - public static final String TABLE_TRACK = "tracks"; - public static final String KEY_TRACK_ID = "_id"; - public static final String KEY_TRACK_NAME = "name"; - public static final String KEY_TRACK_DESCRIPTION = "descr"; - public static final String KEY_TRACK_REMOTE = "remoteId"; - public static final String KEY_TRACK_STATE = "state"; - public static final String KEY_TRACK_CAR_MANUFACTURER = "car_manufacturer"; - public static final String KEY_TRACK_CAR_MODEL = "car_model"; - public static final String KEY_TRACK_CAR_FUEL_TYPE = "fuel_type"; - public static final String KEY_TRACK_CAR_YEAR = "car_construction_year"; - public static final String KEY_TRACK_CAR_ENGINE_DISPLACEMENT = "engine_displacement"; - public static final String KEY_TRACK_CAR_VIN = "vin"; - public static final String KEY_TRACK_CAR_ID = "carId"; - public static final String KEY_TRACK_METADATA = "trackMetadata"; - - public static final String[] ALL_TRACK_KEYS = new String[]{ - KEY_TRACK_ID, - KEY_TRACK_NAME, - KEY_TRACK_DESCRIPTION, - KEY_TRACK_REMOTE, - KEY_TRACK_STATE, - KEY_TRACK_METADATA, - KEY_TRACK_CAR_MANUFACTURER, - KEY_TRACK_CAR_MODEL, - KEY_TRACK_CAR_FUEL_TYPE, - KEY_TRACK_CAR_ENGINE_DISPLACEMENT, - KEY_TRACK_CAR_YEAR, - KEY_TRACK_CAR_VIN, - KEY_TRACK_CAR_ID - }; - - private static final String DATABASE_NAME = "obd2"; - private static final int DATABASE_VERSION = 9; - - private static final String DATABASE_CREATE = "create table " + TABLE_MEASUREMENT + " " + - "(" + KEY_MEASUREMENT_ROWID + " INTEGER primary key autoincrement, " + - KEY_MEASUREMENT_LATITUDE + " BLOB, " + - KEY_MEASUREMENT_LONGITUDE + " BLOB, " + - KEY_MEASUREMENT_TIME + " BLOB, " + - KEY_MEASUREMENT_PROPERTIES + " BLOB, " + - KEY_MEASUREMENT_TRACK + " INTEGER);"; - private static final String DATABASE_CREATE_TRACK = "create table " + TABLE_TRACK + " " + - "(" + KEY_TRACK_ID + " INTEGER primary key, " + - KEY_TRACK_NAME + " BLOB, " + - KEY_TRACK_DESCRIPTION + " BLOB, " + - KEY_TRACK_REMOTE + " BLOB, " + - KEY_TRACK_STATE + " BLOB, " + - KEY_TRACK_METADATA + " BLOB, " + - KEY_TRACK_CAR_MANUFACTURER + " BLOB, " + - KEY_TRACK_CAR_MODEL + " BLOB, " + - KEY_TRACK_CAR_FUEL_TYPE + " BLOB, " + - KEY_TRACK_CAR_ENGINE_DISPLACEMENT + " BLOB, " + - KEY_TRACK_CAR_YEAR + " BLOB, " + - KEY_TRACK_CAR_VIN + " BLOB, " + - KEY_TRACK_CAR_ID + " BLOB);"; - - private static final DateFormat format = SimpleDateFormat.getDateTimeInstance(); - - private static final long DEFAULT_MAX_TIME_BETWEEN_MEASUREMENTS = 1000 * 60 * 15; - - private static final double DEFAULT_MAX_DISTANCE_BETWEEN_MEASUREMENTS = 3.0; - - - private DatabaseHelper mDbHelper; - private SQLiteDatabase mDb; - - @Inject - @InjectApplicationScope - protected Context mContext; - @Inject - protected CarPreferenceHandler mCarManager; - - private Track.TrackId activeTrackReference; - - private long lastMeasurementsInsertionTimestamp; - - private long maxTimeBetweenMeasurements; - - private double maxDistanceBetweenMeasurements; - - private TrackMetadata obdDeviceMetadata; - - public DbAdapterImpl(Context context) throws InstantiationException { - // Inject all annotated fields. - ((Injector) context).injectObjects(this); - - this.maxTimeBetweenMeasurements = DEFAULT_MAX_TIME_BETWEEN_MEASUREMENTS; - this.maxDistanceBetweenMeasurements = DEFAULT_MAX_DISTANCE_BETWEEN_MEASUREMENTS; - - this.mDbHelper = new DatabaseHelper(mContext); - this.mDb = mDbHelper.getWritableDatabase(); - - if (mDb == null) throw new InstantiationException("Database object is null"); - } - - - @Override - public DbAdapter open() { - // deprecated - return this; - } - - @Override - public void close() { - mDb.close(); - mDbHelper.close(); - } - - @Override - public boolean isOpen() { - return mDb.isOpen(); - } - - @Override - public synchronized void insertMeasurement(Measurement measurement) throws - TrackAlreadyFinishedException, MeasurementSerializationException { - insertMeasurement(measurement, false); - } - - @Override - public synchronized void insertMeasurement(Measurement measurement, boolean ignoreFinished) - throws TrackAlreadyFinishedException, MeasurementSerializationException { - if (!ignoreFinished) { - Track tempTrack = getTrack(measurement.getTrackId(), true); - if (tempTrack.isFinished()) { - throw new TrackAlreadyFinishedException("The linked track (" + tempTrack - .getTrackID() + ") is already finished!"); - } - } - - ContentValues values = new ContentValues(); - - values.put(KEY_MEASUREMENT_LATITUDE, measurement.getLatitude()); - values.put(KEY_MEASUREMENT_LONGITUDE, measurement.getLongitude()); - values.put(KEY_MEASUREMENT_TIME, measurement.getTime()); - values.put(KEY_MEASUREMENT_TRACK, measurement.getTrackId().getId()); - String propertiesString; - try { - propertiesString = createJsonObjectForProperties(measurement).toString(); - } catch (JSONException e) { - logger.warn(e.getMessage(), e); - throw new MeasurementSerializationException(e); - } - values.put(KEY_MEASUREMENT_PROPERTIES, propertiesString); - - long res = mDb.insert(TABLE_MEASUREMENT, null, values); - } - - @Override - public synchronized void insertNewMeasurement(Measurement measurement) throws - TrackAlreadyFinishedException, MeasurementSerializationException { - Track.TrackId activeTrack = getActiveTrackReference( - new Position(measurement.getLatitude(), measurement.getLongitude())); - - measurement.setTrackId(activeTrack); - insertMeasurement(measurement); - - lastMeasurementsInsertionTimestamp = System.currentTimeMillis(); - } - - @Override - public synchronized long insertTrack(Track track, boolean remote) { - ContentValues values = createDbEntry(track); - - long result = mDb.insert(TABLE_TRACK, null, values); - track.setTrackID(new Track.TrackId(result)); - - removeMeasurementArtifacts(result); - - for (Measurement m : track.getMeasurements()) { - m.setTrackId(track.getTrackID()); - try { - insertMeasurement(m, remote); - } catch (TrackAlreadyFinishedException e) { - logger.warn(e.getMessage(), e); - } catch (MeasurementSerializationException e) { - logger.warn(e.getMessage(), e); - } - } - - return result; - } - - @Override - public synchronized long insertTrack(Track track) { - return insertTrack(track, false); - } - - private void removeMeasurementArtifacts(long id) { - mDb.delete(TABLE_MEASUREMENT, KEY_MEASUREMENT_TRACK + "='" + id + "'", null); - } - - @Override - public synchronized boolean updateTrack(Track track) { - logger.debug("updateTrack: " + track.getTrackID()); - ContentValues values = createDbEntry(track); - long result = mDb.replace(TABLE_TRACK, null, values); - return (result != -1 ? true : false); - } - - @Override - public ArrayList getAllTracks() { - return getAllTracks(false); - } - - @Override - public ArrayList getAllTracks(boolean lazyMeasurements) { - ArrayList tracks = new ArrayList(); - Cursor c = mDb.query(TABLE_TRACK, new String[]{KEY_TRACK_ID}, null, null, null, null, null); - c.moveToFirst(); - for (int i = 0; i < c.getCount(); i++) { - long id = c.getLong(c.getColumnIndex(KEY_TRACK_ID)); - tracks.add(getTrack(new Track.TrackId(id), lazyMeasurements)); - c.moveToNext(); - } - c.close(); - return tracks; - } - - @Override - public Observable getTrackObservable(boolean lazyMeasurements) { - return null; - } - - @Override - public Track getTrack(Track.TrackId id, boolean lazyMeasurements) { - Cursor c = getCursorForTrackID(id.getId()); - if (!c.moveToFirst()) { - return null; - } - - String remoteId = c.getString(c.getColumnIndex(KEY_TRACK_REMOTE)); - - Track track = new TrackImpl(Track.DownloadState.DOWNLOADED); - if(remoteId != null && !remoteId.isEmpty()){ - track.setRemoteID(remoteId); - } - - track.setTrackID(id); - track.setName(c.getString(c.getColumnIndex(KEY_TRACK_NAME))); - track.setDescription(c.getString(c.getColumnIndex(KEY_TRACK_DESCRIPTION))); - - int statusColumn = c.getColumnIndex(KEY_TRACK_STATE); - if (statusColumn != -1) { - track.setTrackStatus(Track.TrackStatus.valueOf(c.getString(statusColumn))); - } else { - track.setTrackStatus(Track.TrackStatus.FINISHED); - } - - String metadata = c.getString(c.getColumnIndex(KEY_TRACK_METADATA)); - if (metadata != null) { - try { - track.setMetadata(TrackMetadata.fromJson(c.getString(c.getColumnIndex - (KEY_TRACK_METADATA)))); - } catch (JSONException e) { - logger.warn(e.getMessage(), e); - } - } - - track.setCar(createCarFromCursor(c)); - - c.close(); - - if (!lazyMeasurements) { - loadMeasurements(track); - } else { - track.setLazyMeasurements(true); - Measurement first = getFirstMeasurementForTrack(track); - Measurement last = getLastMeasurementForTrack(track); - - if (first != null && last != null) { - track.setStartTime(first.getTime()); - track.setEndTime(last.getTime()); - } - - } - - if (track.isRemoteTrack()) { - /* - * remote tracks are always finished - */ - track.setTrackStatus(Track.TrackStatus.FINISHED); - } - - return track; - } - - private Car createCarFromCursor(Cursor c) { - Log.e("tag", "" + c.getString(c.getColumnIndex(KEY_TRACK_CAR_MANUFACTURER)) + " " + c - .getString(c.getColumnIndex(KEY_TRACK_CAR_ID))); - if (c.getString(c.getColumnIndex(KEY_TRACK_CAR_MANUFACTURER)) == null || - c.getString(c.getColumnIndex(KEY_TRACK_CAR_MODEL)) == null || - // c.getString(c.getColumnIndex(KEY_TRACK_CAR_ID)) == null || - c.getString(c.getColumnIndex(KEY_TRACK_CAR_YEAR)) == null || - c.getString(c.getColumnIndex(KEY_TRACK_CAR_FUEL_TYPE)) == null || - c.getString(c.getColumnIndex(KEY_TRACK_CAR_ENGINE_DISPLACEMENT)) == null) { - return null; - } - - String manufacturer = c.getString(c.getColumnIndex(KEY_TRACK_CAR_MANUFACTURER)); - String model = c.getString(c.getColumnIndex(KEY_TRACK_CAR_MODEL)); - String carId = c.getString(c.getColumnIndex(KEY_TRACK_CAR_ID)); - Car.FuelType fuelType = Car.FuelType.valueOf(c.getString(c.getColumnIndex - (KEY_TRACK_CAR_FUEL_TYPE))); - int engineDisplacement = c.getInt(c.getColumnIndex(KEY_TRACK_CAR_ENGINE_DISPLACEMENT)); - int year = c.getInt(c.getColumnIndex(KEY_TRACK_CAR_YEAR)); - - return new CarImpl(carId, manufacturer, model, fuelType, year, engineDisplacement); - } - - private Measurement getLastMeasurementForTrack(Track track) { - Cursor c = mDb.query(TABLE_MEASUREMENT, ALL_MEASUREMENT_KEYS, - KEY_MEASUREMENT_TRACK + "=\"" + track.getTrackID() + "\"", null, null, null, - KEY_MEASUREMENT_TIME + " DESC", "1"); - - - Measurement measurement = null; - if (c.moveToFirst()) { - measurement = buildMeasurementFromCursor(track, c); - } - - return measurement; - } - - - private Measurement getFirstMeasurementForTrack(Track track) { - Cursor c = mDb.query(TABLE_MEASUREMENT, ALL_MEASUREMENT_KEYS, - KEY_MEASUREMENT_TRACK + "=\"" + track.getTrackID() + "\"", null, null, null, - KEY_MEASUREMENT_TIME + " ASC", "1"); - - Measurement measurement = null; - if (c.moveToFirst()) { - measurement = buildMeasurementFromCursor(track, c); - } - - return measurement; - } - - - private Measurement buildMeasurementFromCursor(Track track, Cursor c) { - double lat = c.getDouble(c.getColumnIndex(KEY_MEASUREMENT_LATITUDE)); - double lon = c.getDouble(c.getColumnIndex(KEY_MEASUREMENT_LONGITUDE)); - long time = c.getLong(c.getColumnIndex(KEY_MEASUREMENT_TIME)); - String rawData = c.getString(c.getColumnIndex(KEY_MEASUREMENT_PROPERTIES)); - Measurement measurement = new MeasurementImpl(lat, lon); - measurement.setTime(time); - measurement.setTrackId(track.getTrackID()); - - if (rawData != null) { - try { - JSONObject json = new JSONObject(rawData); - JSONArray names = json.names(); - if (names != null) { - for (int j = 0; j < names.length(); j++) { - String key = names.getString(j); - measurement.setProperty(Measurement.PropertyKey.valueOf(key), json.getDouble(key)); - } - } - } catch (JSONException e) { - logger.severe("could not load properties", e); - } - } - return measurement; - } - - @Override - public Track getTrack(Track.TrackId id) { - return getTrack(id, false); - } - - @Override - public boolean hasTrack(Track.TrackId id) { - Cursor cursor = getCursorForTrackID(id.getId()); - if (cursor.getCount() > 0) { - return true; - } else { - return false; - } - } - - @Override - public void deleteAllTracks() { - mDb.delete(TABLE_MEASUREMENT, null, null); - mDb.delete(TABLE_TRACK, null, null); - } - - @Override - public int getNumberOfStoredTracks() { - Cursor cursor = mDb.rawQuery("SELECT COUNT(" + KEY_TRACK_ID + ") FROM " + TABLE_TRACK, - null); - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - return count; - } - - @Override - public Track getLastUsedTrack(boolean lazyMeasurements) { - ArrayList trackList = getAllTracks(lazyMeasurements); - if (trackList.size() > 0) { - Track track = trackList.get(trackList.size() - 1); - return track; - } - - return null; - } - - @Override - public Track getLastUsedTrack() { - return getLastUsedTrack(false); - } - - @Override - public void deleteTrack(Track.TrackId id) { - logger.debug("deleteLocalTrack: " + id); - mDb.delete(TABLE_TRACK, KEY_TRACK_ID + "='" + id + "'", null); - removeMeasurementArtifacts(id.getId()); - } - - @Override - public int getNumberOfRemoteTracks() { - Cursor cursor = mDb.rawQuery("SELECT COUNT(" + KEY_TRACK_REMOTE + ") FROM " + - TABLE_TRACK, null); - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - return count; - } - - @Override - public int getNumberOfLocalTracks() { - // TODO Auto-generated method stub - logger.warn("implement it!!!"); - return 0; - } - - @Override - public void deleteAllLocalTracks() { - // TODO Auto-generated method stub - logger.warn("implement it!!!"); - } - - @Override - public void deleteAllRemoteTracks() { - Cursor cursor = mDb.rawQuery("SELECT COUNT(" + KEY_TRACK_REMOTE + ") FROM " + - TABLE_TRACK, null); - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - logger.info("" + count); - mDb.delete(TABLE_TRACK, KEY_TRACK_REMOTE + " IS NOT NULL", null); - } - - @Override - public List getAllLocalTracks() { - return getAllLocalTracks(false); - } - - @Override - public List getAllLocalTracks(boolean lazyMeasurements) { - ArrayList tracks = new ArrayList(); - Cursor c = mDb.query(TABLE_TRACK, ALL_TRACK_KEYS, KEY_TRACK_REMOTE + " IS NULL", null, - null, null, null); - c.moveToFirst(); - for (int i = 0; i < c.getCount(); i++) { - tracks.add(getTrack(new Track.TrackId(c.getLong(c.getColumnIndex(KEY_TRACK_ID))), - lazyMeasurements)); - c.moveToNext(); - } - c.close(); - return tracks; - } - - @Override - public List getAllRemoteTracks() { - return getAllRemoteTracks(false); - } - - @Override - public List getAllRemoteTracks(boolean lazyMeasurements) { - ArrayList tracks = new ArrayList<>(); - Cursor c = mDb.query(TABLE_TRACK, ALL_TRACK_KEYS, KEY_TRACK_REMOTE + " IS NOT NULL", - null, null, null, null); - c.moveToFirst(); - for (int i = 0; i < c.getCount(); i++) { - tracks.add(getTrack(new Track.TrackId(c.getLong(c.getColumnIndex(KEY_TRACK_ID))), - lazyMeasurements)); - c.moveToNext(); - } - c.close(); - return tracks; - } - - private ContentValues createDbEntry(Track track) { - ContentValues values = new ContentValues(); - if (track.getTrackID() != null && track.getTrackID().getId() != 0) { - values.put(KEY_TRACK_ID, track.getTrackID().getId()); - } - values.put(KEY_TRACK_NAME, track.getName()); - values.put(KEY_TRACK_DESCRIPTION, track.getDescription()); - if (track.isRemoteTrack()) { - values.put(KEY_TRACK_REMOTE, track.getRemoteID()); - } - values.put(KEY_TRACK_STATE, track.getTrackStatus().toString()); - if (track.getCar() != null) { - values.put(KEY_TRACK_CAR_MANUFACTURER, track.getCar().getManufacturer()); - values.put(KEY_TRACK_CAR_MODEL, track.getCar().getModel()); - values.put(KEY_TRACK_CAR_FUEL_TYPE, track.getCar().getFuelType().name()); - values.put(KEY_TRACK_CAR_ID, track.getCar().getId()); - values.put(KEY_TRACK_CAR_ENGINE_DISPLACEMENT, track.getCar().getEngineDisplacement()); - values.put(KEY_TRACK_CAR_YEAR, track.getCar().getConstructionYear()); - } - - if (track.getMetadata() != null) { - try { - values.put(KEY_TRACK_METADATA, track.getMetadata().toJsonString()); - } catch (JSONException e) { - logger.warn(e.getMessage(), e); - } - } - - return values; - } - - public JSONObject createJsonObjectForProperties(Measurement measurement) throws JSONException { - JSONObject result = new JSONObject(); - - Map properties = measurement.getAllProperties(); - for (Measurement.PropertyKey key : properties.keySet()) { - result.put(key.name(), properties.get(key)); - } - - return result; - } - - private Cursor getCursorForTrackID(long id) { - Cursor cursor = mDb.query(TABLE_TRACK, ALL_TRACK_KEYS, KEY_TRACK_ID + " = \"" + id + - "\"", null, null, null, null); - return cursor; - } - - @Override - public List getAllMeasurementsForTrack(Track track) { - ArrayList allMeasurements = new ArrayList(); - - Cursor c = mDb.query(TABLE_MEASUREMENT, ALL_MEASUREMENT_KEYS, - KEY_MEASUREMENT_TRACK + "=\"" + track.getTrackID() + "\"", null, null, null, - KEY_MEASUREMENT_TIME + " ASC"); - - if (!c.moveToFirst()) { - return Collections.emptyList(); - } - - for (int i = 0; i < c.getCount(); i++) { - - Measurement measurement = buildMeasurementFromCursor(track, c); - allMeasurements.add(measurement); - c.moveToNext(); - } - - c.close(); - - return allMeasurements; - } - - @Override - public Track createNewTrack() { - finishCurrentTrack(); - - String date = format.format(new Date()); - Car car = mCarManager.getCar(); - Track track = new TrackImpl(Track.DownloadState.DOWNLOADED); - track.setCar(car); - track.setName("Track " + date); - track.setDescription(String.format(mContext.getString(R.string.default_track_description) - , car != null ? car.getModel() : "null")); - insertTrack(track); - return track; - } - - @Override - public synchronized Track finishCurrentTrack() { - Track last = getLastUsedTrack(); - if (last != null) { - try { - last.getLastMeasurement(); - } catch (NoMeasurementsException e) { - deleteTrack(last.getTrackID()); - } - - last.setTrackStatus(Track.TrackStatus.FINISHED); - updateTrack(last); - - if (last.getTrackID().equals(activeTrackReference)) { - logger.info("removing activeTrackReference: " + activeTrackReference); - } else { - logger.info(String.format( - "Finished track did not have the same ID as the activeTrackReference. " + - "Finished: %s vs. active: %s", - last.getTrackID(), activeTrackReference)); - } - - activeTrackReference = null; - } - return last; - } - - @Override - public void updateCarIdOfTracks(String currentId, String newId) { - ContentValues newValues = new ContentValues(); - newValues.put(KEY_TRACK_CAR_ID, newId); - - mDb.update(TABLE_TRACK, newValues, KEY_TRACK_CAR_ID + "=?", new String[]{currentId}); - } - - @Override - public synchronized Track.TrackId getActiveTrackReference(Position pos) { - /* - * make this performant. if we have an activeTrackReference - * and its not too old, use it - */ - if (activeTrackReference != null && - System.currentTimeMillis() - lastMeasurementsInsertionTimestamp < this - .maxTimeBetweenMeasurements / 10) { - logger.info("returning activeTrackReference: " + activeTrackReference); - return activeTrackReference; - } - - Track lastUsed = getLastUsedTrack(true); - - if (!trackIsStillActive(lastUsed, pos)) { - lastUsed = createNewTrack(); - } - - logger.info( - String.format("getActiveTrackReference - Track: %s / id: %s", - lastUsed.getName(), - lastUsed.getTrackID())); - - activeTrackReference = lastUsed.getTrackID(); - - setConnectedOBDDevice(this.obdDeviceMetadata); - - return activeTrackReference; - } - - /** - * This method determines whether it is necessary to create a new track or - * of the current/last used track should be reused - */ - private boolean trackIsStillActive(Track lastUsedTrack, Position location) { - - logger.info("trackIsStillActive: last? " + (lastUsedTrack == null ? "null" : - lastUsedTrack.toString())); - - // New track if last measurement is more than 60 minutes - // ago - - try { - if (lastUsedTrack != null && lastUsedTrack.getTrackStatus() != Track.TrackStatus.FINISHED && - lastUsedTrack.getLastMeasurement() != null) { - - if ((System.currentTimeMillis() - lastUsedTrack - .getLastMeasurement().getTime()) > this.maxTimeBetweenMeasurements) { - logger.info(String.format("Should create a new track: last measurement is more " + - "than %d mins ago", - (int) (this.maxTimeBetweenMeasurements / 1000 / 60))); - return false; - } - - // new track if last position is significantly different - // from the current position (more than 3 km) - else if (location == null || Util.getDistance(lastUsedTrack.getLastMeasurement() - .getLatitude(), lastUsedTrack.getLastMeasurement().getLongitude(), - location.getLatitude(), location.getLongitude()) > this - .maxDistanceBetweenMeasurements) { - logger.info(String.format("Should create a new track: last measurement's position" + - " is more than %f km away", - this.maxDistanceBetweenMeasurements)); - return false; - } - - // TODO: New track if user clicks on create new track button - - // TODO: new track if VIN changed - - else { - logger.info("Should append to the last track: last measurement is close enough in" + - " space/time"); - return true; - } - - } else { - logger.info("should craete a new track?"); -// logger.info(String.format("Should create new Track. Last was null? %b; Last status " + -// "was: %s; Last measurement: %s", -// lastUsedTrack == null, -// lastUsedTrack == null ? "n/a" : lastUsedTrack.getTrackStatus().toString(), -// lastUsedTrack == null ? "n/a" : lastUsedTrack.getLastMeasurement())); - - if (lastUsedTrack != null && !lastUsedTrack.isRemoteTrack()) { - List measurements = lastUsedTrack.getMeasurements(); - if (measurements == null || measurements.isEmpty()) { - logger.info(String.format("Track %s did not contain measurements and will not" + - " be used. Deleting!", lastUsedTrack.getTrackID())); - // deleteLocalTrack(lastUsedTrack.getTrackId()); - } - } - - return false; - } - } catch (NoMeasurementsException e) { - logger.warn(e.getMessage(), e); - return false; - } - } - - @Override - public TrackMetadata updateTrackMetadata(Track.TrackId trackId, TrackMetadata trackMetadata) { - Track tempTrack = getTrack(trackId, true); - TrackMetadata result = tempTrack.updateMetadata(trackMetadata); - updateTrack(tempTrack); - return result; - } - - @Override - public void transitLocalToRemoteTrack(Track track, String remoteId) { - ContentValues newValues = new ContentValues(); - newValues.put(KEY_TRACK_REMOTE, remoteId); - - mDb.update(TABLE_TRACK, newValues, KEY_TRACK_ID + "=?", new String[]{ - Long.toString(track.getTrackID().getId()) - }); - } - - @Override - public void loadMeasurements(Track track) { - List measurements = getAllMeasurementsForTrack(track); - track.setMeasurements(measurements); - track.setLazyMeasurements(false); - } - - @Override - public void setConnectedOBDDevice(TrackMetadata obdDeviceMetadata) { - this.obdDeviceMetadata = obdDeviceMetadata; - - if (this.obdDeviceMetadata != null && this.activeTrackReference != null) { - updateTrackMetadata(activeTrackReference, this.obdDeviceMetadata); - } - } - - private static class DatabaseHelper extends SQLiteOpenHelper { - - DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(DATABASE_CREATE); - db.execSQL(DATABASE_CREATE_TRACK); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - logger.info("Upgrading database from version " + oldVersion + " to " + newVersion + - ", which will destroy all old data"); - db.execSQL("DROP TABLE IF EXISTS measurements"); - db.execSQL("DROP TABLE IF EXISTS tracks"); - onCreate(db); - } - } -} diff --git a/org.envirocar.app/src/org/envirocar/app/storage/LazyLoadingStrategy.java b/org.envirocar.app/src/org/envirocar/app/storage/LazyLoadingStrategy.java deleted file mode 100644 index f4eb6bcc2..000000000 --- a/org.envirocar.app/src/org/envirocar/app/storage/LazyLoadingStrategy.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.storage; - -import org.envirocar.core.entity.Track; - -/** - * An interface for providing a strategy to lazy load memory consuming - * resources. - * - */ -public interface LazyLoadingStrategy { - - /** - * an implementation shall load all measurements - * for the given track. after succesful loading, - * {@link Track#setLazyMeasurements(boolean)} with - * false shall be set. - * - * @param track the track - */ - void lazyLoadMeasurements(Track track); - -} diff --git a/org.envirocar.app/src/org/envirocar/app/storage/LazyLoadingStrategyImpl.java b/org.envirocar.app/src/org/envirocar/app/storage/LazyLoadingStrategyImpl.java deleted file mode 100644 index 94883df73..000000000 --- a/org.envirocar.app/src/org/envirocar/app/storage/LazyLoadingStrategyImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.storage; - -import android.content.Context; - -import org.envirocar.core.entity.Track; -import org.envirocar.core.injection.Injector; - -import javax.inject.Inject; - -/** - * - */ -public class LazyLoadingStrategyImpl implements LazyLoadingStrategy { - - @Inject - protected DbAdapter mDBAdapter; - - /** - * Constructor. - * - * @param context the context of this scope. - */ - public LazyLoadingStrategyImpl(Context context) { - ((Injector) context).injectObjects(this); - } - - @Override - public void lazyLoadMeasurements(Track track) { - mDBAdapter.loadMeasurements(track); - track.setLazyMeasurements(false); - } - -} diff --git a/org.envirocar.app/src/org/envirocar/app/view/LoginActivity.java b/org.envirocar.app/src/org/envirocar/app/view/LoginActivity.java index d01944664..a361a5b0f 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/LoginActivity.java +++ b/org.envirocar.app/src/org/envirocar/app/view/LoginActivity.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -43,22 +43,21 @@ import com.afollestad.materialdialogs.MaterialDialog; import org.envirocar.app.R; -import org.envirocar.app.TrackHandler; import org.envirocar.app.handler.TermsOfUseManager; +import org.envirocar.app.handler.TrackDAOHandler; import org.envirocar.app.handler.UserHandler; +import org.envirocar.app.view.utils.ECAnimationUtils; import org.envirocar.app.views.TypefaceEC; import org.envirocar.core.dao.TrackDAO; +import org.envirocar.core.entity.TermsOfUse; import org.envirocar.core.entity.User; import org.envirocar.core.entity.UserImpl; -import org.envirocar.core.exception.DataRetrievalFailureException; import org.envirocar.core.exception.DataUpdateFailureException; import org.envirocar.core.exception.ResourceConflictException; -import org.envirocar.core.exception.UnauthorizedException; import org.envirocar.core.injection.BaseInjectorActivity; import org.envirocar.core.logging.Logger; import org.envirocar.remote.DAOProvider; -import java.util.ArrayList; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -66,8 +65,8 @@ import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; -import rx.Observable; import rx.Scheduler; +import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action0; @@ -85,6 +84,8 @@ public class LoginActivity extends BaseInjectorActivity { protected Toolbar mToolbar; @InjectView(R.id.activity_login_exp_toolbar) protected Toolbar mExpToolbar; + @InjectView(R.id.activity_login_logo_dump) + protected View mLogoView; @InjectView(R.id.activity_login_exp_toolbar_content) protected View mExpToolbarContent; @@ -131,7 +132,7 @@ public class LoginActivity extends BaseInjectorActivity { @Inject protected TermsOfUseManager mTermsOfUseManager; @Inject - protected TrackHandler mTrackHandler; + protected TrackDAOHandler mTrackDAOHandler; private final Scheduler.Worker mMainThreadWorker = AndroidSchedulers .mainThread().createWorker(); @@ -140,6 +141,7 @@ public class LoginActivity extends BaseInjectorActivity { private Subscription mLoginSubscription; private Subscription mRegisterSubscription; + private Subscription mTermsOfUseSubscription; private Subscription mStatisticsDownloadSubscription; @Override @@ -162,6 +164,7 @@ protected void onCreate(Bundle savedInstanceState) { mLoginCard.setVisibility(View.GONE); mStatisticsListView.setVisibility(View.GONE); mExpToolbarContent.setVisibility(View.GONE); + mLogoView.setVisibility(View.INVISIBLE); } @Override @@ -315,8 +318,7 @@ public void onSuccess(User user) { updateView(true); // Then ask for terms of use acceptance. - mBackgroundWorker.schedule(() -> mTermsOfUseManager - .askForTermsOfUseAcceptance(user, LoginActivity.this, null)); + askForTermsOfUseAcceptance(); }); } @@ -340,6 +342,39 @@ public void onUnableToCommunicateServer() { } } + private void askForTermsOfUseAcceptance() { + // Unsubscribe before issueing a new request. + if(mTermsOfUseSubscription != null && !mTermsOfUseSubscription.isUnsubscribed()) + mTermsOfUseSubscription.unsubscribe(); + + mTermsOfUseSubscription = mTermsOfUseManager.verifyTermsOfUse(LoginActivity.this) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Subscriber() { + @Override + public void onStart() { + LOG.info("onStart() verifying terms of use"); + } + + @Override + public void onCompleted() { + LOG.info("onCompleted() verifying terms of use"); + } + + @Override + public void onError(Throwable e) { + LOG.warn(e.getMessage(), e); + } + + @Override + public void onNext(TermsOfUse termsOfUse) { + LOG.info(String.format( + "User has accepted the terms of use -> [%s]", + termsOfUse.getIssuedDate())); + } + }); + } + @OnClick(R.id.activity_account_register_button) protected void onRegisterAccountButtonClicked() { mRegisterUsername.setError(null); @@ -426,13 +461,10 @@ protected void onRegisterAccountButtonClicked() { newUser.setMail(email); mDAOProvider.getUserDAO().createUser(newUser); - // Successfully created the user mMainThreadWorker.schedule(() -> { // Set the new user as the logged in user. mUserManager.setUser(newUser); - mTermsOfUseManager.askForTermsOfUseAcceptance( - newUser, LoginActivity.this, null); // Update the view, i.e., hide the registration card and show the profile // page. @@ -446,6 +478,8 @@ protected void onRegisterAccountButtonClicked() { getResources().getString(R.string.welcome_message), username), Snackbar.LENGTH_LONG).show(); }); + + askForTermsOfUseAcceptance(); } catch (ResourceConflictException e) { LOG.warn(e.getMessage(), e); @@ -493,7 +527,7 @@ private void logOut() { mUserManager.logOut(); // Finally, delete all tracks that are associated to the previous user. - mTrackHandler.deleteAllRemoteTracksLocally(); + mTrackDAOHandler.deleteAllRemoteTracksLocally(); // Close the dialog. dialog.dismiss(); @@ -522,6 +556,8 @@ private void logOut() { animateHideView(mNoStatisticsInfo, R.anim.fade_out, null); } + ECAnimationUtils.animateHideView(this, mLogoView, R.anim.fade_out); + // hide the no statistics info if it is visible. if (mStatisticsProgressView.getVisibility() == View.VISIBLE) { animateHideView(mStatisticsProgressView, R.anim.fade_out, null); @@ -562,13 +598,17 @@ private void updateView(boolean isLoggedIn) { if (mLoginCard.getVisibility() == View.VISIBLE) { slideOutLoginCard(); } + // If the register card is visible, then slide it out. if (mRegisterCard.getVisibility() == View.VISIBLE) { slideOutRegisterCard(); } - // If the statistics progess view is not visible, then fade it in. - if (mStatisticsProgressView.getVisibility() != View.VISIBLE) { - animateViewTransition(mStatisticsProgressView, R.anim.fade_in, false); +// // If the statistics progess view is not visible, then fade it in. +// if (mStatisticsProgressView.getVisibility() != View.VISIBLE) { +// animateViewTransition(mStatisticsProgressView, R.anim.fade_in, false); +// } + if(mLogoView.getVisibility() != View.VISIBLE){ + ECAnimationUtils.animateShowView(this, mLogoView, R.anim.fade_in); } // Update the Gravatar image. @@ -580,6 +620,14 @@ private void updateView(boolean isLoggedIn) { mAccountImage.setImageBitmap(bitmap); }); + // update the local track count. + mTrackDAOHandler.getLocalTrackCount() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(integer -> { + mLocalTrackNumber.setText("" + integer); + }); + // Update the new values of the exp toolbar content. mBackgroundWorker.schedule(() -> { try { @@ -597,40 +645,44 @@ private void updateView(boolean isLoggedIn) { } }); - Observable.just(true) - .map(aBoolean -> { - try { - return mDAOProvider - .getUserStatisticsDAO() - .getUserStatistics(user) - .getStatistics(); - } catch (UnauthorizedException e) { - LOG.warn("The user is unauthorized to access this endpoint.", e); - } catch (DataRetrievalFailureException e) { - LOG.warn("Error while trying to retrive user statistics.", e); - mMainThreadWorker.schedule(() -> - animateHideView(mStatisticsProgressView, R.anim.fade_out, - () -> animateViewTransition(mNoStatisticsInfo, R - .anim.fade_in, false))); - } - return null; - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(statistics -> { - if (statistics == null || statistics.isEmpty()) { - animateHideView(mStatisticsProgressView, R.anim.fade_out, - () -> animateViewTransition(mNoStatisticsInfo, - R.anim.fade_in, false)); - } else { - mStatisticsListView.setAdapter(new UserStatisticsAdapter - (LoginActivity.this, - new ArrayList<>(statistics.values()))); - animateHideView(mStatisticsProgressView, R.anim.fade_out, - () -> animateViewTransition(mStatisticsListView, - R.anim.fade_in, false)); - } - }); +// animateHideView(mStatisticsProgressView, R.anim.fade_out, +// () -> animateViewTransition(mNoStatisticsInfo, R +// .anim.fade_in, false))); + +// Observable.just(true) +// .map(aBoolean -> { +// try { +// return mDAOProvider +// .getUserStatisticsDAO() +// .getUserStatistics(user) +// .getStatistics(); +// } catch (UnauthorizedException e) { +// LOG.warn("The user is unauthorized to access this endpoint.", e); +// } catch (DataRetrievalFailureException e) { +// LOG.warn("Error while trying to retrive user statistics.", e); +// mMainThreadWorker.schedule(() -> +// animateHideView(mStatisticsProgressView, R.anim.fade_out, +// () -> animateViewTransition(mNoStatisticsInfo, R +// .anim.fade_in, false))); +// } +// return null; +// }) +// .subscribeOn(Schedulers.io()) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe(statistics -> { +// if (statistics == null || statistics.isEmpty()) { +// animateHideView(mStatisticsProgressView, R.anim.fade_out, +// () -> animateViewTransition(mNoStatisticsInfo, +// R.anim.fade_in, false)); +// } else { +// mStatisticsListView.setAdapter(new UserStatisticsAdapter +// (LoginActivity.this, +// new ArrayList<>(statistics.values()))); +// animateHideView(mStatisticsProgressView, R.anim.fade_out, +// () -> animateViewTransition(mStatisticsListView, +// R.anim.fade_in, false)); +// } +// }); } } diff --git a/org.envirocar.app/src/org/envirocar/app/view/RegisterFragment.java b/org.envirocar.app/src/org/envirocar/app/view/RegisterFragment.java index 268fca844..311839a1f 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/RegisterFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/RegisterFragment.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -25,6 +25,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentManager; import android.text.TextUtils; import android.view.KeyEvent; @@ -41,6 +42,7 @@ import org.envirocar.app.handler.UserHandler; import org.envirocar.app.view.dashboard.RealDashboardFragment; import org.envirocar.app.views.TypefaceEC; +import org.envirocar.core.entity.TermsOfUse; import org.envirocar.core.entity.User; import org.envirocar.core.entity.UserImpl; import org.envirocar.core.exception.DataUpdateFailureException; @@ -51,8 +53,10 @@ import javax.inject.Inject; -import de.keyboardsurfer.android.widget.crouton.Crouton; -import de.keyboardsurfer.android.widget.crouton.Style; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Activity which displays a register screen to the user, offering registration @@ -60,7 +64,7 @@ */ public class RegisterFragment extends BaseInjectorFragment { - private static final Logger logger = Logger.getLogger(RegisterFragment.class); + private static final Logger LOG = Logger.getLogger(RegisterFragment.class); /** @@ -83,6 +87,8 @@ public class RegisterFragment extends BaseInjectorFragment { private View mRegisterStatusView; private TextView mRegisterStatusMessageView; + private Subscription mRegisterSubscription; + // Injected Variables @Inject protected DAOProvider mDAOProvider; @@ -141,6 +147,16 @@ public void onViewCreated(View view, Bundle savedInstanceState) { mUsernameView.requestFocus(); } + @Override + public void onDestroy() { + LOG.info("onDestroy()"); + + if (mRegisterSubscription != null || !mRegisterSubscription.isUnsubscribed()) + mRegisterSubscription.unsubscribe(); + + super.onDestroy(); + } + /** * Attempts to sign in or register the account specified by the register * form. If there are form errors (invalid email, missing fields, etc.), the @@ -192,7 +208,8 @@ public void attemptRegister() { mEmailView.setError(getString(R.string.error_field_required)); focusView = mEmailView; cancel = true; - } else if (!mEmail.matches("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$")) { + } else if (!mEmail.matches("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\" + + ".[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$")) { mEmailView.setError(getString(R.string.error_invalid_email)); focusView = mEmailView; cancel = true; @@ -220,7 +237,7 @@ public void attemptRegister() { if (cancel) { // There was an error; don't attempt register and focus the first // form field with an error. - focusView.requestFocus(); + focusView.requestFocus(); } else { //hide the keyboard InputMethodManager imm = (InputMethodManager) getActivity().getSystemService( @@ -292,20 +309,49 @@ protected Void doInBackground(Void... params) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { - Crouton.makeText(getActivity(), getResources().getString(R.string.welcome_message) + mUsername, Style.CONFIRM).show(); + Snackbar.make(getView(), getResources().getString(R.string + .welcome_message) + mUsername, Snackbar.LENGTH_LONG).show(); + User user = new UserImpl(mUsername, mPassword); mUserManager.setUser(user); - mTermsOfUseManager.askForTermsOfUseAcceptance(user, getActivity(), null); - - getActivity().getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); + mRegisterSubscription = mTermsOfUseManager.verifyTermsOfUse(getActivity()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Subscriber() { + @Override + public void onStart() { + LOG.info("onStart() verifying terms of use"); + } + + @Override + public void onCompleted() { + LOG.info("onCompleted() verifying terms of use"); + } + + @Override + public void onError(Throwable e) { + LOG.warn(e.getMessage(), e); + } + + @Override + public void onNext(TermsOfUse termsOfUse) { + LOG.info(String.format( + "User has accepted the terms of use -> [%s]", + termsOfUse.getIssuedDate())); + onCompleted(); + } + }); + + getActivity().getSupportFragmentManager().popBackStack(null, + FragmentManager.POP_BACK_STACK_INCLUSIVE); getActivity().getSupportFragmentManager().beginTransaction() .replace(R.id.content_frame, mDashboardFragment) .commit(); } }); } catch (DataUpdateFailureException e) { - logger.warn(e.getMessage(), e); + LOG.warn(e.getMessage(), e); getActivity().runOnUiThread(new Runnable() { @Override @@ -318,7 +364,7 @@ public void run() { }); } catch (ResourceConflictException e) { - logger.warn(e.getMessage(), e); + LOG.warn(e.getMessage(), e); getActivity().runOnUiThread(new Runnable() { @Override public void run() { @@ -345,7 +391,8 @@ protected void onPostExecute(Void result) { /* * Use this method to sign up a new user */ - public boolean createUser(String user, String token, String mail) throws ResourceConflictException, DataUpdateFailureException { + public boolean createUser(String user, String token, String mail) throws + ResourceConflictException, DataUpdateFailureException { User newUser = new UserImpl(user, token); newUser.setMail(mail); mDAOProvider.getUserDAO().createUser(newUser); diff --git a/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionActivity.java b/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionActivity.java index 6a74018fb..5264065fd 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionActivity.java +++ b/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionActivity.java @@ -1,70 +1,54 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.app.view.carselection; import android.content.Context; -import android.graphics.Point; import android.os.Bundle; -import android.os.Handler; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; -import android.support.design.widget.TextInputLayout; import android.support.v7.widget.Toolbar; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.Display; import android.view.MenuItem; import android.view.View; -import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.Button; import android.widget.ListView; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.Toast; - import org.envirocar.app.R; import org.envirocar.app.handler.CarPreferenceHandler; -import org.envirocar.remote.DAOProvider; +import org.envirocar.app.handler.UserHandler; import org.envirocar.app.view.utils.ECAnimationUtils; import org.envirocar.core.entity.Car; -import org.envirocar.core.entity.CarImpl; import org.envirocar.core.injection.BaseInjectorActivity; import org.envirocar.core.logging.Logger; +import org.envirocar.remote.DAOProvider; -import java.util.Arrays; -import java.util.Calendar; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; -import rx.Observer; -import rx.Scheduler; +import rx.Observable; +import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; @@ -72,7 +56,7 @@ /** * @author dewall */ -public class CarSelectionActivity extends BaseInjectorActivity { +public class CarSelectionActivity extends BaseInjectorActivity implements CarSelectionUiListener { private static final Logger LOG = Logger.getLogger(CarSelectionActivity.class); private static final int DURATION_SHEET_ANIMATION = 350; @@ -83,58 +67,33 @@ public class CarSelectionActivity extends BaseInjectorActivity { protected Toolbar mToolbar; @InjectView(R.id.activity_car_selection_layout_exptoolbar) protected Toolbar mExpToolbar; - + @InjectView(R.id.actvity_car_selection_layout_loading) + protected View loadingView; @InjectView(R.id.overlay) - protected View mOverlay; - // @InjectView(R.id.activity_car_selection_new_car_sheet) - // protected View mSheetView; + protected View overlayView; - @InjectView(R.id.activity_car_selection_new_car_card) - protected View mNewCarCard; @InjectView(R.id.activity_car_selection_new_car_fab) protected FloatingActionButton mFab; @InjectView(R.id.activity_car_selection_layout_carlist) protected ListView mCarListView; - // Views of the sheet view used to add a new car type. - @InjectView(R.id.activity_car_selection_model_input_layout) - protected TextInputLayout mModelTextLayout; - @InjectView(R.id.activity_car_selection_manufacturer_edit_text) - protected AutoCompleteTextView mManufacturerTextView; - @InjectView(R.id.activity_car_selection_model_edit_text) - protected AutoCompleteTextView mModelTextView; - @InjectView(R.id.activity_car_selection_year_edit_text) - protected AutoCompleteTextView mYearTextView; - @InjectView(R.id.activity_car_selection_engine_edit_text) - protected AutoCompleteTextView mEngineTextView; - @InjectView(R.id.activity_car_selection_new_car_card_add_button) - protected Button mAddCarButton; - - @InjectView(R.id.activity_car_selection_new_car_card_radio_group) - protected RadioGroup mRadioGroup; - @InjectView(R.id.activity_car_selection_new_car_card_radio_gasoline) - protected RadioButton mRadioGasoline; - @InjectView(R.id.activity_car_selection_new_car_card_radio_diesel) - protected RadioButton mDieselGasoline; - @Inject protected DAOProvider mDAOProvider; @Inject protected CarPreferenceHandler mCarManager; + @Inject + protected UserHandler mUserHandler; + + private CarSelectionAddCarFragment addCarFragment; private Set mCars = new HashSet<>(); - private Set mManufacturerNames; - private Map> mCarToModelMap = new ConcurrentHashMap<>(); - private Map> mModelToYear = new ConcurrentHashMap<>(); - private Map> mModelToCCM = new ConcurrentHashMap<>(); - private Scheduler.Worker mMainThreadWorker = AndroidSchedulers.mainThread().createWorker(); - private Subscription mSensoreSubscription; private CarSelectionListAdapter mCarListAdapter; - private AutoCompleteArrayAdapter mManufacturerNameAdapter; + private Subscription loadingCarsSubscription; + @Override protected void onCreate(Bundle savedInstanceState) { @@ -151,34 +110,7 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - // Initialize the manufacturer names and its textview adapter - mManufacturerNames = new HashSet(Arrays.asList(getResources() - .getStringArray(R.array.car_types))); - mManufacturerNameAdapter = new AutoCompleteArrayAdapter(CarSelectionActivity.this, - android.R.layout.simple_dropdown_item_1line, mManufacturerNames.toArray( - new String[mManufacturerNames.size()])); - - // Set the adapter. - mManufacturerTextView.setAdapter(mManufacturerNameAdapter); - mManufacturerTextView.setOnClickListener(v -> - mManufacturerTextView.showDropDown()); - - // Init the text watcher that are responsible to update the edit text views. - initTextWatcher(); - - // Init the hide keyboard listener. These always hide the keyboard once an item has been - // selected in the autocomplete list. - setupHideKeyboardListener(); setupListView(); - dispatchRemoteSensors(); - - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mSensoreSubscription != null) - mSensoreSubscription.unsubscribe(); } @Override @@ -196,8 +128,10 @@ public boolean onOptionsItemSelected(MenuItem item) { @Override public void onBackPressed() { - // if the sheet view was not visible. - if (!closeAddCarCard()) { + // if the add car fragment is visible. + if (addCarFragment != null && addCarFragment.isVisible()) { + addCarFragment.closeThisFragment(); + } else { // call the super method. super.onBackPressed(); } @@ -207,112 +141,19 @@ public void onBackPressed() { // gets shown. @OnClick(R.id.activity_car_selection_new_car_fab) public void onClickNewCarButton() { - showAddCarCard(); + showAddCarFragment(); } - /** - * Add car button onClick listener. When clicked, it tries to find out if the car already - * exists. If this is the case, then it adds the car to the list of selected cars. If not, - * then it selects - */ - @OnClick(R.id.activity_car_selection_new_car_card_add_button) - public void onClickAddCarButton() { - // TODO Check views. - String manufacturer = mManufacturerTextView.getText().toString(); - String model = mModelTextView.getText().toString(); - String yearString = mYearTextView.getText().toString(); - String engineString = mEngineTextView.getText().toString(); - Car.FuelType fuelType = mRadioGasoline.isChecked() ? Car.FuelType.GASOLINE : Car - .FuelType.DIESEL; - - View focusView = null; - - //First check all input forms for empty strings - if (engineString == null || engineString.isEmpty()) { - mEngineTextView.setError("Cannot be empty"); - focusView = mEngineTextView; - } - if (yearString == null || yearString.isEmpty()) { - mYearTextView.setError("Cannot be empty"); - focusView = mYearTextView; - } - if (model == null || model.isEmpty()) { - mModelTextView.setError("Cannot be empty"); - focusView = mModelTextView; - } - if (manufacturer == null || manufacturer.isEmpty()) { - mManufacturerTextView.setError("Cannot be empty"); - focusView = mManufacturerTextView; - } - - // if any of the input forms contained empty values, then set the focus to the last one set. - if (focusView != null) { - focusView.requestFocus(); - return; - } - - - int year = Integer.parseInt(mYearTextView.getText().toString()); - int engine = Integer.parseInt(mEngineTextView.getText().toString()); - int currentYear = Calendar.getInstance().get(Calendar.YEAR); - - // Check the values of engine and year for validity. - if (engine < 500 || engine > 5000) { - mEngineTextView.setError("Invalid value"); - focusView = mEngineTextView; - } - if (year < 1990 || year > currentYear) { - mYearTextView.setError("Invalid value"); - focusView = mYearTextView; - } - - // if tengine or year have invalid values, then request the focus. - if (focusView != null) { - focusView.requestFocus(); - return; - } - - Car selectedCar = null; - if (mManufacturerNames.contains(manufacturer) - && mCarToModelMap.get(manufacturer) != null - && mCarToModelMap.get(manufacturer).contains(model) - && mModelToCCM.get(model) != null - && mModelToCCM.get(model).contains("" + engine) - && mModelToYear.get(model) != null - && mModelToYear.get(model).contains("" + year)) { - for (Car car : mCars) { - if (car.getManufacturer().equals(manufacturer) - && car.getModel().equals(model) - && car.getConstructionYear() == year - && car.getEngineDisplacement() == engine - && car.getFuelType() == fuelType) { - selectedCar = car; - } - } - } + @Override + protected void onDestroy() { + LOG.info("onDestroy()"); - if (selectedCar == null) { - selectedCar = new CarImpl(manufacturer, model, fuelType, year, engine); - mCarManager.registerCarAtServer(selectedCar); + if (this.loadingCarsSubscription != null && + !this.loadingCarsSubscription.isUnsubscribed()) { + this.loadingCarsSubscription.unsubscribe(); } - // When the car has been successfully inserted in the listadapter, then update - // the list adapter. - if (mCarManager.addCar(selectedCar)) { - // Add the car to the adapter and close the sheet view. - mCarListAdapter.addCarItem(selectedCar); - closeAddCarCard(); - - // Schedule a show snackbar runnable when the sheet animation has been finished. - new Handler().postDelayed(() -> showSnackbar("Car successfully created!"), - DURATION_SHEET_ANIMATION); - resetEditTexts(); - } - // Otherwise, when the list already contained the specific car type, then show a - // snackbar. - else { - showSnackbar("Car is already in the list"); - } + super.onDestroy(); } /** @@ -320,26 +161,19 @@ public void onClickAddCarButton() { * * @return true if the card view was not shown. */ - private boolean showAddCarCard() { - // If the card view is not visible... - if (!mNewCarCard.isShown()) { - // Get the height of the display - Display display = getWindowManager().getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - int height = size.y; - - // expand the toolbar. - ECAnimationUtils.expandView(mExpToolbar, height / 3); - // Start an animation that shows the card view. - ECAnimationUtils.animateShowView(this, mNewCarCard, - R.anim.translate_in_bottom_login_card); - ECAnimationUtils.animateHideView(this, mFab, R.anim.fade_out); - return true; + private boolean showAddCarFragment() { + if (this.addCarFragment != null && this.addCarFragment.isVisible()) { + LOG.info("addCarFragment is already visible."); + return false; } + ECAnimationUtils.animateShowView(this, overlayView, R.anim.fade_in); + this.addCarFragment = new CarSelectionAddCarFragment(); + getSupportFragmentManager().beginTransaction() + .replace(R.id.activity_car_selection_container, this.addCarFragment) + .commit(); // this card was already visible. Therefore, return false. - return false; + return true; } /** @@ -348,253 +182,91 @@ private boolean showAddCarCard() { * @return true if the sheet view as visible and has been */ private boolean closeAddCarCard() { - // If the card view is visible. - if (mNewCarCard.isShown()) { - // start an animation that hides the card view. - ECAnimationUtils.animateHideView(this, mNewCarCard, R.anim.translate_out_bottom_card, - // When the animation is finished, show the FAB - () -> ECAnimationUtils.animateShowView( - CarSelectionActivity.this, mFab, R.anim.fade_in)); - ECAnimationUtils.compressView(mExpToolbar, 1); - + if (this.addCarFragment != null && this.addCarFragment.isVisible()) { + getSupportFragmentManager() + .beginTransaction() + .remove(addCarFragment) + .commit(); + addCarFragment = null; return true; } - // the card view was not visible. Therefore, return false. return false; } private void setupListView() { Car selectedCar = mCarManager.getCar(); - List usedCars = mCarManager.getDeserialzedCars(); - mCarListAdapter = new CarSelectionListAdapter(this, selectedCar, usedCars, new - CarSelectionListAdapter - .OnCarListActionCallback() { + List usedCars = new ArrayList<>(); + + mCarListAdapter = new CarSelectionListAdapter(this, selectedCar, usedCars, + new CarSelectionListAdapter.OnCarListActionCallback() { @Override public void onSelectCar(Car car) { mCarManager.setCar(car); - showSnackbar(String.format("%s %s selected as my car", + showSnackbar(String.format(getString(R.string.car_selection_car_selected), car.getManufacturer(), car.getModel())); } @Override public void onDeleteCar(Car car) { - LOG.info(String.format("onDeleteCar(%s %s %s %s)", car.getManufacturer - (), car - .getModel(), "" + car.getConstructionYear(), "" + car - .getEngineDisplacement())); + LOG.info(String.format("onDeleteCar(%s %s %s %s)", + car.getManufacturer(), car.getModel(), + "" + car.getConstructionYear(), + "" + car.getEngineDisplacement())); // If the car has been removed successfully... if (mCarManager.removeCar(car)) { // then remove it from the list and show a snackbar. mCarListAdapter.removeCarItem(car); - showSnackbar(String.format("%s %s has been deleted!", car - .getManufacturer(), car.getModel())); + showSnackbar(String.format( + getString(R.string.car_selection_car_deleted_tmp), + car.getManufacturer(), car.getModel())); } } }); mCarListView.setAdapter(mCarListAdapter); - } - - /** - * Setups the listener to hide the keyboards. - */ - private void setupHideKeyboardListener() { - mManufacturerTextView.setOnItemClickListener((parent, view, position, id) -> { - InputMethodManager in = (InputMethodManager) getSystemService(Context - .INPUT_METHOD_SERVICE); - in.hideSoftInputFromWindow(mManufacturerTextView.getWindowToken(), 0); - }); - - mModelTextView.setOnItemClickListener((parent, view, position, id) -> { - InputMethodManager in = (InputMethodManager) getSystemService(Context - .INPUT_METHOD_SERVICE); - in.hideSoftInputFromWindow(mModelTextView.getWindowToken(), 0); - }); - - mYearTextView.setOnItemClickListener((parent, view, position, id) -> { - InputMethodManager in = (InputMethodManager) getSystemService(Context - .INPUT_METHOD_SERVICE); - in.hideSoftInputFromWindow(mYearTextView.getWindowToken(), 0); - }); - - mEngineTextView.setOnItemClickListener((parent, view, position, id) -> { - InputMethodManager in = (InputMethodManager) getSystemService(Context - .INPUT_METHOD_SERVICE); - in.hideSoftInputFromWindow(mEngineTextView.getWindowToken(), 0); - }); - } - - private void dispatchRemoteSensors() { - mSensoreSubscription = - mDAOProvider.getSensorDAO() - .getAllCarsObservable() - .onBackpressureBuffer(10000) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.computation()) - .subscribe(new Observer>() { - @Override - public void onCompleted() { - mMainThreadWorker.schedule(() -> { - Toast.makeText(CarSelectionActivity.this, "Received! " + - mCars.size(), Toast.LENGTH_SHORT).show(); - mManufacturerNameAdapter = new AutoCompleteArrayAdapter( - CarSelectionActivity.this, - android.R.layout.simple_dropdown_item_1line, - mManufacturerNames.toArray( - new String[mManufacturerNames.size()])); - mManufacturerTextView.setAdapter(mManufacturerNameAdapter); - mSensoreSubscription.unsubscribe(); - }); - } - - @Override - public void onError(Throwable e) { - LOG.error(e.getMessage(), e); - mMainThreadWorker.schedule(() -> - Toast.makeText(CarSelectionActivity.this, "ERROR!", Toast - .LENGTH_SHORT).show()); - } - - @Override - public void onNext(List cars) { - for (Car car : cars) { - if (car != null) - addCarToAutocompleteList(car); - } - } - }); - } + loadingCarsSubscription = mCarManager.getAllDeserializedCars() + .flatMap(cars -> { + Observable> carsObs = Observable.just(cars); + if (mUserHandler.isLoggedIn() && !mCarManager.isDownloaded()) { + LOG.info("Loading Cars: user has not downloaded its remote cars. " + + "Trying to fetch these."); + carsObs = carsObs.concatWith(mCarManager.downloadRemoteCarsOfUser()); + } + return carsObs; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Subscriber>() { + @Override + public void onStart() { + LOG.info("onStart()"); + loadingView.setVisibility(View.VISIBLE); + } - /** - * Resets the edittexts to empty strings. - */ - private void resetEditTexts() { - mManufacturerTextView.setText(""); - mModelTextView.setText(""); - mYearTextView.setText(""); - mEngineTextView.setText(""); - } - - /** - * Inserts the attributes of the car - * - * @param car - */ - private void addCarToAutocompleteList(Car car) { - mCars.add(car); - String manufacturer = car.getManufacturer(); - String model = car.getModel(); - mManufacturerNames.add(manufacturer); - - if (!mCarToModelMap.containsKey(manufacturer)) - mCarToModelMap.put(manufacturer, new HashSet<>()); - mCarToModelMap.get(manufacturer).add(model); - - if (!mModelToYear.containsKey(model)) - mModelToYear.put(model, new HashSet<>()); - mModelToYear.get(model).add(Integer.toString(car.getConstructionYear())); - - if (!mModelToCCM.containsKey(model)) - mModelToCCM.put(model, new HashSet<>()); - mModelToCCM.get(model).add(Integer.toString(car.getEngineDisplacement())); - } - - private void initTextWatcher() { - mManufacturerTextView.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // Nothing to do.. - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // Nothing to do.. - } - - @Override - public void afterTextChanged(Editable s) { - Set modelTypes = mCarToModelMap.get(s.toString()); - - mModelTextView.setText(""); - mModelTextView.setAdapter(null); - mModelTextView.dismissDropDown(); - - mYearTextView.setText(""); - mYearTextView.setAdapter(null); - mYearTextView.dismissDropDown(); - - mEngineTextView.setText(""); - mEngineTextView.setAdapter(null); - mEngineTextView.dismissDropDown(); - - if (modelTypes != null && modelTypes.size() > 0) { - AutoCompleteArrayAdapter adapter = new AutoCompleteArrayAdapter( - CarSelectionActivity.this, - android.R.layout.simple_dropdown_item_1line, - modelTypes.toArray(new String[modelTypes.size()])); - - mModelTextView.setAdapter(adapter); - mModelTextView.showDropDown(); - } - } - }); - - mModelTextView.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // nothing to do... - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // nothing to do... - } - - @Override - public void afterTextChanged(Editable s) { - String model = s.toString(); - Set modelYear = mModelToYear.get(model); - if (modelYear != null && modelYear.size() > 0) { - AutoCompleteArrayAdapter adapter = new AutoCompleteArrayAdapter( - CarSelectionActivity.this, - android.R.layout.simple_dropdown_item_1line, - modelYear.toArray(new String[modelYear.size()])); - - mYearTextView.setAdapter(adapter); - mYearTextView.showDropDown(); - } - - Set engine = mModelToCCM.get(model); - if (engine != null && modelYear.size() > 0) { - AutoCompleteArrayAdapter adapter = new AutoCompleteArrayAdapter( - CarSelectionActivity.this, - android.R.layout.simple_dropdown_item_1line, - engine.toArray(new String[engine.size()])); - - mEngineTextView.setAdapter(adapter); - } - } - }); - - mYearTextView.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } + @Override + public void onCompleted() { + LOG.info("onCompleted() loading of all cars"); + loadingView.setVisibility(View.INVISIBLE); + } - @Override - public void afterTextChanged(Editable s) { - mEngineTextView.showDropDown(); - } - }); + @Override + public void onError(Throwable e) { + LOG.error(e.getMessage(), e); + loadingView.setVisibility(View.INVISIBLE); + } + @Override + public void onNext(List cars) { + LOG.info("onNext() " + cars.size()); + for (Car car : cars) { + if (!usedCars.contains(car)) + usedCars.add(car); + } + mCarListAdapter.notifyDataSetInvalidated(); + } + }); } /** @@ -606,6 +278,29 @@ private void showSnackbar(String msg) { Snackbar.make(mFab, msg, Snackbar.LENGTH_LONG).show(); } + /** + * Hides the AddCarFragment + */ + @Override + public void onHideAddCarFragment() { + LOG.info("hideAddCarFragment()"); + closeAddCarCard(); + } + + @Override + public void onCarAdded(Car car) { + LOG.info("onCarAdded(Car)"); + + if (mCarManager.addCar(car)) { + mCarListAdapter.addCarItem(car); + showSnackbar(String.format(getString(R.string.car_selection_successfully_added_tmp), + car.getManufacturer(), car.getModel())); + } else { + showSnackbar(String.format(getString(R.string.car_selection_already_in_list_tmp), + car.getManufacturer(), car.getModel())); + } + } + /** * Array adapter for the automatic completion of the AutoCompleteTextView. The intention of diff --git a/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionAddCarFragment.java b/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionAddCarFragment.java new file mode 100644 index 000000000..484b857b0 --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionAddCarFragment.java @@ -0,0 +1,710 @@ +package org.envirocar.app.view.carselection; + +import android.graphics.Point; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Pair; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.Spinner; +import android.widget.TextView; + +import com.jakewharton.rxbinding.support.v7.widget.RxToolbar; + +import org.envirocar.app.R; +import org.envirocar.app.handler.CarPreferenceHandler; +import org.envirocar.app.view.utils.ECAnimationUtils; +import org.envirocar.core.entity.Car; +import org.envirocar.core.entity.CarImpl; +import org.envirocar.core.injection.BaseInjectorFragment; +import org.envirocar.core.logging.Logger; +import org.envirocar.remote.DAOProvider; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.inject.Inject; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import rx.Scheduler; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class CarSelectionAddCarFragment extends BaseInjectorFragment { + private static final Logger LOG = Logger.getLogger(CarSelectionAddCarFragment.class); + + @InjectView(R.id.activity_car_selection_newcar_toolbar) + protected Toolbar toolbar; + @InjectView(R.id.activity_car_selection_newcar_toolbar_exp) + protected View toolbarExp; + @InjectView(R.id.activity_car_selection_newcar_content_view) + protected View contentView; + @InjectView(R.id.activity_car_selection_newcar_download_layout) + protected View downloadView; + + @InjectView(R.id.activity_car_selection_newcar_manufacturer) + protected TextView manufacturerText; + @InjectView(R.id.activity_car_selection_newcar_manufacturer_spinner) + protected Spinner manufacturerSpinner; + + @InjectView(R.id.activity_car_selection_newcar_model) + protected TextView modelText; + @InjectView(R.id.activity_car_selection_newcar_model_spinner) + protected Spinner modelSpinner; + + @InjectView(R.id.activity_car_selection_newcar_year) + protected TextView yearText; + @InjectView(R.id.activity_car_selection_newcar_year_spinner) + protected Spinner yearSpinner; + + @InjectView(R.id.activity_car_selection_newcar_engine) + protected TextView engineText; + @InjectView(R.id.activity_car_selection_newcar_engine_spinner) + protected Spinner engineSpinner; + + @InjectView(R.id.activity_car_selection_newcar_radio_group) + protected RadioGroup fuelTypeRadioGroup; + @InjectView(R.id.activity_car_selection_newcar_radio_group_gasoline) + protected RadioButton gasolineRadio; + @InjectView(R.id.activity_car_selection_newcar_radio_group_diesel) + protected RadioButton dieselRadio; + + @Inject + protected DAOProvider daoProvider; + @Inject + protected CarPreferenceHandler carManager; + + private Subscription sensorsSubscription; + private Subscription createCarSubscription; + private Scheduler.Worker mainThreadWorker = AndroidSchedulers.mainThread().createWorker(); + + private Set mCars = new HashSet<>(); + private Set mManufacturerNames = new HashSet<>(); + private Map> mCarToModelMap = new ConcurrentHashMap<>(); + private Map> mModelToYear = new ConcurrentHashMap<>(); + private Map, Set> mModelToCCM = new ConcurrentHashMap<>(); + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle + savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + View view = inflater.inflate( + R.layout.activity_car_selection_newcar_fragment, container, false); + ButterKnife.inject(this, view); + + // Get the display size in pixels + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + int width = size.x; + + // Set the dropdown width of the spinner to half of the display pixel width. + manufacturerSpinner.setDropDownWidth(width / 2); + modelSpinner.setDropDownWidth(width / 2); + yearSpinner.setDropDownWidth(width / 2); + engineSpinner.setDropDownWidth(width / 2); + + toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp); + toolbar.inflateMenu(R.menu.menu_logbook_add_fueling); + toolbar.setNavigationOnClickListener(v -> closeThisFragment()); + + + // initially we set the toolbar exp to gone + toolbar.setVisibility(View.GONE); + toolbarExp.setVisibility(View.GONE); + contentView.setVisibility(View.GONE); + downloadView.setVisibility(View.INVISIBLE); + + createCarSubscription = RxToolbar.itemClicks(toolbar) + .filter(continueWhenFormIsCorrect()) + .map(createCarFromForm()) + .filter(continueWhenCarHasCorrectValues()) + .map(checkCarAlreadyExist()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + LOG.info("onCompleted car"); + } + + @Override + public void onError(Throwable e) { + LOG.warn(e.getMessage(), e); + } + + @Override + public void onNext(Car car) { + LOG.info("car added"); + ((CarSelectionUiListener) getActivity()).onCarAdded(car); + closeThisFragment(); + } + }); + + + dispatchRemoteSensors(); + + initFocusChangedListener(); + initTextWatcher(); + return view; + } + + @Override + public void onResume() { + LOG.info("onResume()"); + super.onResume(); + ECAnimationUtils.animateShowView(getContext(), toolbar, + R.anim.translate_slide_in_top_fragment); + ECAnimationUtils.animateShowView(getContext(), toolbarExp, + R.anim.translate_slide_in_top_fragment); + ECAnimationUtils.animateShowView(getContext(), contentView, + R.anim.translate_slide_in_bottom_fragment); + } + + @Override + public void onDestroy() { + LOG.info("onDestroy()"); + + if (sensorsSubscription != null && !sensorsSubscription.isUnsubscribed()) { + sensorsSubscription.unsubscribe(); + } + if (createCarSubscription != null && !createCarSubscription.isUnsubscribed()) { + createCarSubscription.unsubscribe(); + } + + super.onDestroy(); + } + + + /** + * Add car button onClick listener. When clicked, it tries to find out if the car already + * exists. If this is the case, then it adds the car to the list of selected cars. If not, + * then it selects + */ + private Func1 continueWhenFormIsCorrect() { + return menuItem -> { + // First, reset the form + manufacturerText.setError(null); + modelText.setError(null); + yearText.setError(null); + engineText.setError(null); + + View focusView = null; + + //First check all input forms for empty strings + if (engineText.getText().length() == 0) { + engineText.setError("Cannot be empty"); + focusView = engineText; + } + if (yearText.getText().length() == 0) { + yearText.setError("Cannot be empty"); + focusView = yearText; + } + if (modelText.getText().length() == 0) { + modelText.setError("Cannot be empty"); + focusView = modelText; + } + if (manufacturerText.getText().length() == 0) { + manufacturerText.setError("Cannot be empty"); + focusView = manufacturerText; + } + + // if any of the input forms contained empty values, then set the focus to the + // last one set. + if (focusView != null) { + LOG.info("Some input fields were empty"); + focusView.requestFocus(); + return false; + } else { + return true; + } + }; + } + + private Func1 createCarFromForm() { + return t -> { + // Get the values + String manufacturer = manufacturerText.getText().toString(); + String model = modelText.getText().toString(); + String yearString = yearText.getText().toString(); + String engineString = engineText.getText().toString(); + Car.FuelType fuelType = gasolineRadio.isChecked() ? + Car.FuelType.GASOLINE : Car.FuelType.DIESEL; + + // create the car + return new CarImpl(manufacturer, model, fuelType, + Integer.parseInt(yearString), Integer.parseInt(engineString)); + }; + } + + private Func1 continueWhenCarHasCorrectValues() { + return car -> { + int currentYear = Calendar.getInstance().get(Calendar.YEAR); + View focusView = null; + + // Check the values of engine and year for validity. + if (car.getEngineDisplacement() < 500 || car.getEngineDisplacement() > 5000) { + engineText.setError("Invalid value"); + focusView = engineText; + } + if (car.getConstructionYear() < 1990 || car.getConstructionYear() > currentYear) { + yearText.setError("Invalid value"); + focusView = yearText; + } + + // if tengine or year have invalid values, then request the focus. + if (focusView != null) { + focusView.requestFocus(); + return false; + } + + return true; + }; + } + + private Func1 checkCarAlreadyExist() { + return car -> { + String manu = car.getManufacturer(); + String model = car.getModel(); + String year = "" + car.getConstructionYear(); + String engine = "" + car.getEngineDisplacement(); + Pair modelYear = new Pair<>(model, year); + + Car selectedCar = null; + if (mManufacturerNames.contains(manu) + && mCarToModelMap.get(manu) != null + && mCarToModelMap.get(manu).contains(model) + && mModelToYear.get(model) != null + && mModelToYear.get(model).contains(year) + && mModelToCCM.get(modelYear) != null + && mModelToCCM.get(modelYear).contains(engine)) { + for (Car other : mCars) { + if (other.getManufacturer().equals(manu) + && other.getModel().equals(model) + && other.getConstructionYear() == car.getConstructionYear() + && other.getEngineDisplacement() == car.getEngineDisplacement() + && other.getFuelType() == car.getFuelType()) { + selectedCar = other; + break; + } + } + } + + if (selectedCar == null) { + LOG.info("New Car type. Register car at server."); + carManager.registerCarAtServer(car); + return car; + } else { + LOG.info(String.format("Car already existed -> [%s]", selectedCar.getId())); + return selectedCar; + } + }; + } + + private void dispatchRemoteSensors() { + sensorsSubscription = daoProvider.getSensorDAO() + .getAllCarsObservable() + .onBackpressureBuffer(10000) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe(new Subscriber>() { + @Override + public void onStart() { + LOG.info("onStart() download sensors"); + downloadView.setVisibility(View.VISIBLE); + } + + @Override + public void onCompleted() { + LOG.info("onCompleted(): cars successfully downloaded."); + + mainThreadWorker.schedule(() -> { + // Update the manufactuerers in + updateSpinner(mManufacturerNames, manufacturerSpinner); + + // Set the initial selection of the manufacturer to NO SELECTION + manufacturerSpinner.setSelection(Adapter.NO_SELECTION, true); + + // Initialize the spinner. + initSpinner(); + unsubscribe(); + + downloadView.setVisibility(View.INVISIBLE); + }); + } + + @Override + public void onError(Throwable e) { + LOG.error(e.getMessage(), e); + mainThreadWorker.schedule(() -> { + downloadView.setVisibility(View.INVISIBLE); + }); + } + + @Override + public void onNext(List cars) { + for (Car car : cars) { + if (car != null) + addCarToAutocompleteList(car); + } + } + }); + } + + private void initTextWatcher() { + manufacturerText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Nothing to do.. + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // nothing to do.. + } + + @Override + public void afterTextChanged(Editable s) { + manufacturerText.setError(null); + + modelText.setText(""); + yearText.setText(""); + engineText.setText(""); + + modelSpinner.setAdapter(null); + yearSpinner.setAdapter(null); + engineSpinner.setAdapter(null); + } + }); + + modelText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Nothing to do.. + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Nothing to do.. + } + + @Override + public void afterTextChanged(Editable s) { + modelText.setError(null); + + yearText.setText(""); + engineText.setText(""); + } + }); + + yearText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Nothing to do.. + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Nothing to do.. + } + + @Override + public void afterTextChanged(Editable s) { + yearText.setError(null); + + engineText.setText(""); + } + }); + + engineText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Nothing to do.. + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Nothing to do.. + } + + @Override + public void afterTextChanged(Editable s) { + engineText.setError(null); + } + }); + } + + private void initFocusChangedListener() { + manufacturerText.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + String manufacturer = manufacturerText.getText().toString(); + updateModelViews(manufacturer); + } + }); + + modelText.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + String model = modelText.getText().toString(); + updateYearView(model); + } + }); + + yearText.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + String year = yearText.getText().toString(); + String model = modelText.getText().toString(); + Pair modelYear = new Pair<>(model, year); + + updateEngineView(modelYear); + } + }); + + engineText.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + checkFuelingType(); + } + }); + } + + private void initSpinner() { + manufacturerSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String manufacturer = parent.getItemAtPosition(position).toString(); + LOG.info(String.format("manufactuererSpinner.onItemSelected(%s)", manufacturer)); + + // update the manufacturer textview. + manufacturerText.setText(manufacturer); + ((TextView) view).setText(null); + + // update the model views + updateModelViews(manufacturer); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + modelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String model = parent.getItemAtPosition(position).toString(); + LOG.info(String.format("modelSpinner.onItemSelected(%s)", model)); + + modelText.setText(model); + ((TextView) view).setText(null); + + updateYearView(model); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + yearSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String year = parent.getItemAtPosition(position).toString(); + LOG.info(String.format("yearSpinner.onItemSelected(%s)", year)); + + yearText.setText(year); + ((TextView) view).setText(null); + + updateEngineView(new Pair<>(modelText.getText().toString(), year)); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + engineSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String engine = parent.getItemAtPosition(position).toString(); + LOG.info(String.format("engineSpinner.onItemSelected(%s)", engine)); + + engineText.setText(engine); + ((TextView) view).setText(null); + + checkFuelingType(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + } + + private void checkFuelingType() { + String manufacturer = manufacturerText.getText().toString(); + String model = modelText.getText().toString(); + String yearString = yearText.getText().toString(); + String engineString = engineText.getText().toString(); + Pair modelYear = new Pair<>(model, yearString); + + Car selectedCar = null; + if (mManufacturerNames.contains(manufacturer) + && mCarToModelMap.get(manufacturer) != null + && mCarToModelMap.get(manufacturer).contains(model) + && mModelToYear.get(model) != null + && mModelToYear.get(model).contains(yearString) + && mModelToCCM.get(modelYear) != null + && mModelToCCM.get(modelYear).contains(engineString)) { + for (Car other : mCars) { + if (other.getManufacturer() == null || + other.getModel() == null || + other.getConstructionYear() == 0 || + other.getEngineDisplacement() == 0 || + other.getFuelType() == null) { + continue; + } + if (other.getManufacturer().equals(manufacturer) + && other.getModel().equals(model) + && other.getConstructionYear() == Integer.parseInt(yearString) + && other.getEngineDisplacement() == Integer.parseInt(engineString)) { + selectedCar = other; + break; + } + } + } + + if (selectedCar != null && + selectedCar.getFuelType() != null && + selectedCar.getFuelType() == Car.FuelType.DIESEL) { + dieselRadio.setChecked(true); + } else { + gasolineRadio.setChecked(true); + } + } + + private void updateManufacturerViews() { + if (!mManufacturerNames.isEmpty()) { + updateSpinner(mManufacturerNames, manufacturerSpinner); + } else { + modelSpinner.setAdapter(null); + } + } + + private void updateModelViews(String manufacturer) { + if (mCarToModelMap.containsKey(manufacturer)) { + updateSpinner(mCarToModelMap.get(manufacturer), modelSpinner); + } else { + modelSpinner.setAdapter(null); + } + } + + private void updateYearView(String model) { + if (mModelToYear.containsKey(model)) { + updateSpinner(mModelToYear.get(model), yearSpinner); + } else { + yearSpinner.setAdapter(null); + } + } + + private void updateEngineView(Pair model) { + if (mModelToCCM.containsKey(model)) { + updateSpinner(mModelToCCM.get(model), engineSpinner); + } else { + engineSpinner.setAdapter(null); + } + } + + private void updateAutoComplete(Set toSet, TextView textView) { + List list = new ArrayList<>(); + list.addAll(toSet); + Collections.sort(list); + +// ArrayAdapter + } + + private void updateSpinner(Set toSet, Spinner spinner) { + List list = new ArrayList<>(); + list.addAll(toSet); + Collections.sort(list); + + ArrayAdapter adapter = new ArrayAdapter<>( + getActivity(), + R.layout.activity_car_selection_newcar_spinner_item, + list.toArray(new String[list.size()])); + + spinner.setAdapter(adapter); + } + + /** + * Inserts the attributes of the car + * + * @param car + */ + private void addCarToAutocompleteList(Car car) { + + mCars.add(car); + String manufacturer = car.getManufacturer(); + String model = car.getModel(); + String year = "" + car.getConstructionYear(); + + if (!mManufacturerNames.contains(manufacturer)) + mManufacturerNames.add(manufacturer); + + if (!mCarToModelMap.containsKey(manufacturer)) + mCarToModelMap.put(manufacturer, new HashSet<>()); + mCarToModelMap.get(manufacturer).add(model); + + if (!mModelToYear.containsKey(model)) + mModelToYear.put(model, new HashSet<>()); + mModelToYear.get(model).add(Integer.toString(car.getConstructionYear())); + + Pair modelYearPair = new Pair<>(model, year); + if (!mModelToCCM.containsKey(modelYearPair)) + mModelToCCM.put(modelYearPair, new HashSet<>()); + mModelToCCM.get(modelYearPair).add(Integer.toString(car.getEngineDisplacement())); + } + + public void closeThisFragment() { + // ^^ + ECAnimationUtils.animateHideView(getContext(), + ((CarSelectionActivity) getActivity()).overlayView, R.anim.fade_out); + ECAnimationUtils.animateHideView(getContext(), R.anim + .translate_slide_out_top_fragment, toolbar, toolbarExp); + ECAnimationUtils.animateHideView(getContext(), contentView, R.anim + .translate_slide_out_bottom, new Action0() { + @Override + public void call() { + ((CarSelectionUiListener) getActivity()).onHideAddCarFragment(); + } + }); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionUiListener.java b/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionUiListener.java new file mode 100644 index 000000000..2ec8fc425 --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/view/carselection/CarSelectionUiListener.java @@ -0,0 +1,15 @@ +package org.envirocar.app.view.carselection; + +import org.envirocar.core.entity.Car; + +/** + * TODO JavaDoc + * + * @author dewall + */ +interface CarSelectionUiListener { + + void onHideAddCarFragment(); + + void onCarAdded(Car car); +} diff --git a/org.envirocar.app/src/org/envirocar/app/view/dashboard/DashboardMainFragment.java b/org.envirocar.app/src/org/envirocar/app/view/dashboard/DashboardMainFragment.java index 4e65d4f96..0d3386494 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/dashboard/DashboardMainFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/dashboard/DashboardMainFragment.java @@ -19,13 +19,9 @@ package org.envirocar.app.view.dashboard; import android.bluetooth.BluetoothDevice; -import android.content.ComponentName; -import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.graphics.Color; import android.os.Bundle; -import android.os.IBinder; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.design.widget.Snackbar; @@ -36,24 +32,22 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.squareup.otto.Subscribe; import org.envirocar.app.R; -import org.envirocar.app.TrackHandler; import org.envirocar.app.handler.BluetoothHandler; import org.envirocar.app.handler.CarPreferenceHandler; import org.envirocar.app.handler.LocationHandler; +import org.envirocar.app.handler.TrackRecordingHandler; import org.envirocar.app.services.OBDConnectionService; import org.envirocar.core.events.NewCarTypeSelectedEvent; import org.envirocar.core.events.bluetooth.BluetoothStateChangedEvent; import org.envirocar.core.events.gps.GpsStateChangedEvent; import org.envirocar.core.injection.BaseInjectorFragment; import org.envirocar.core.logging.Logger; -import org.envirocar.core.utils.ServiceUtils; import org.envirocar.obd.events.BluetoothServiceStateChangedEvent; import org.envirocar.obd.service.BluetoothServiceState; @@ -79,7 +73,7 @@ public class DashboardMainFragment extends BaseInjectorFragment { @Inject protected CarPreferenceHandler mCarManager; @Inject - protected TrackHandler mTrackHandler; + protected TrackRecordingHandler mTrackRecordingHandler; @Inject protected LocationHandler mLocationHandler; @@ -94,31 +88,6 @@ public class DashboardMainFragment extends BaseInjectorFragment { private MaterialDialog mConnectingDialog; - - // Defines callbacks for remoteService binding, passed to bindService() - private ServiceConnection mOBDConnectionServiceCon = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - LOG.info("onServiceConnected(): Bound remoteService connected."); - // successfully bounded to the remoteService, cast the binder interface to - // get the remoteService. - Snackbar.make(mStartStopButton, "Connected", Snackbar.LENGTH_LONG).show(); - OBDConnectionService.OBDConnectionBinder binder = (OBDConnectionService - .OBDConnectionBinder) service; - mOBDConnectionService = binder.getService(); - mIsOBDConnectionBounded = true; - Toast.makeText(getActivity(), "CONNECTED", Toast.LENGTH_LONG).show(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - LOG.info("onServiceDisconnected(): Bound remoteService disconnected."); - // Service has been disconnected. - mOBDConnectionService = null; - mIsOBDConnectionBounded = false; - } - }; - private Scheduler.Worker mMainThreadScheduler = AndroidSchedulers.mainThread().createWorker(); private BluetoothServiceState mServiceState = BluetoothServiceState.SERVICE_STOPPED; @@ -172,10 +141,14 @@ public void onResume() { @Override public void onDestroyView() { if (!getActivity().isFinishing() && mDashboardSettingsFragment != null) { - getFragmentManager().beginTransaction() - .remove(mDashboardSettingsFragment) - .remove(mDashboardHeaderFragment) - .commitAllowingStateLoss(); + try { + getFragmentManager().beginTransaction() + .remove(mDashboardSettingsFragment) + .remove(mDashboardHeaderFragment) + .commitAllowingStateLoss(); + } catch (IllegalStateException e){ + LOG.warn(e.getMessage(), e); + } } super.onDestroyView(); } @@ -305,7 +278,7 @@ private void onButtonStopClicked() { .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { - mTrackHandler.finishCurrentTrack(); + mTrackRecordingHandler.finishCurrentTrack(); } }) .show(); @@ -561,7 +534,7 @@ private void hideFragment(Fragment fragment, int enterAnimation, int exitAnimati @UiThread private void onServiceStarting() { - bindService(); +// bindService(); } @UiThread @@ -589,7 +562,7 @@ private void onServiceStarted() { @UiThread private void onServiceStopping() { - unbindService(); +// unbindService(); } @UiThread @@ -610,26 +583,26 @@ private void onServiceStopped() { updateStartToStopButton(); } - /** - * Creates a binding for the {@link OBDConnectionService}. - */ - private void bindService() { - // if the remoteService is currently running, then bind to the remoteService. - if (ServiceUtils.isServiceRunning(getActivity(), OBDConnectionService.class)) { - Toast.makeText(getActivity(), "is Running", Toast.LENGTH_SHORT).show(); - Intent intent = new Intent(getActivity(), OBDConnectionService.class); - getActivity().bindService(intent, mOBDConnectionServiceCon, Context.BIND_AUTO_CREATE); - } - } - - private void unbindService() { - // If it is bounded, then unbind the remoteService. - if (mIsOBDConnectionBounded) { - LOG.info("onStop(): disconnect bound remoteService"); - getActivity().unbindService(mOBDConnectionServiceCon); - mIsOBDConnectionBounded = false; - } - } +// /** +// * Creates a binding for the {@link OBDConnectionService}. +// */ +// private void bindService() { +// // if the remoteService is currently running, then bind to the remoteService. +// if (ServiceUtils.isServiceRunning(getActivity(), OBDConnectionService.class)) { +// Toast.makeText(getActivity(), "is Running", Toast.LENGTH_SHORT).show(); +// Intent intent = new Intent(getActivity(), OBDConnectionService.class); +// getActivity().bindService(intent, mOBDConnectionServiceCon, Context.BIND_AUTO_CREATE); +// } +// } +// +// private void unbindService() { +// // If it is bounded, then unbind the remoteService. +// if (mIsOBDConnectionBounded) { +// LOG.info("onStop(): disconnect bound remoteService"); +// getActivity().unbindService(mOBDConnectionServiceCon); +// mIsOBDConnectionBounded = false; +// } +// } @UiThread private void updateStartToStopButton() { diff --git a/org.envirocar.app/src/org/envirocar/app/view/dashboard/DashboardTempomatFragment.java b/org.envirocar.app/src/org/envirocar/app/view/dashboard/DashboardTempomatFragment.java index ed2bcff86..749025112 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/dashboard/DashboardTempomatFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/dashboard/DashboardTempomatFragment.java @@ -97,7 +97,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { */ @Subscribe public void onReceiveSpeedUpdateEvent(SpeedUpdateEvent event) { - LOG.debug(String.format("Received event: %s", event.toString())); + //LOG.debug(String.format("Received event: %s", event.toString())); if(mTempomatView != null){ mTempomatView.setSpeed(event.mSpeed); } diff --git a/org.envirocar.app/src/org/envirocar/app/view/dashboard/RealDashboardFragment.java b/org.envirocar.app/src/org/envirocar/app/view/dashboard/RealDashboardFragment.java index ce9bd3e15..a1cb196c8 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/dashboard/RealDashboardFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/dashboard/RealDashboardFragment.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -26,6 +26,7 @@ import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -42,7 +43,7 @@ import org.envirocar.app.handler.BluetoothHandler; import org.envirocar.app.handler.CarPreferenceHandler; import org.envirocar.app.handler.PreferenceConstants; -import org.envirocar.app.view.settings.NewSettingsActivity; +import org.envirocar.app.view.settings.SettingsActivity; import org.envirocar.app.views.LayeredImageRotateView; import org.envirocar.app.views.TypefaceEC; import org.envirocar.core.entity.Car; @@ -64,7 +65,6 @@ import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; -import de.keyboardsurfer.android.widget.crouton.Crouton; import rx.Scheduler; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; @@ -158,7 +158,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) { // .getDefaultSharedPreferences(getActivity())) // .subscribeOn(Schedulers.computation()) // .observeOn(AndroidSchedulers.mainThread()) -// .filter(prefKey -> PreferenceConstants.PREFERENCE_TAG_CAR.equals(prefKey) || +// .preProcess(prefKey -> PreferenceConstants.PREFERENCE_TAG_CAR.equals(prefKey) || // PreferenceConstants.CAR_HASH_CODE.equals(prefKey) || // PreferenceConstants.PREF_BLUETOOTH_LIST.equals(prefKey)) // .subscribe(prefKey -> { @@ -182,8 +182,8 @@ public void onResume() { Car car = mCarManager.getCar(); if (car != null && car.getFuelType() == Car.FuelType.DIESEL) { - Crouton.makeText(getActivity(), R.string.diesel_not_yet_supported, - de.keyboardsurfer.android.widget.crouton.Style.ALERT).show(); + Snackbar.make(getView(), R.string.diesel_not_yet_supported, Snackbar.LENGTH_LONG) + .show(); } mUseImperialUnits = PreferenceManager.getDefaultSharedPreferences(getActivity()) @@ -289,7 +289,7 @@ protected void onConnectionStateImageClicked() { public void onClickDashboardStartStop() { if (mBluetoothHandler.isBluetoothEnabled()) { if (mCarManager.getCar() == null) { - Intent settingsIntent = new Intent(getActivity(), NewSettingsActivity.class); + Intent settingsIntent = new Intent(getActivity(), SettingsActivity.class); startActivity(settingsIntent); } else { /* @@ -365,7 +365,7 @@ public void run() { * Updates the drawbale of the GpsFix ImageView depending on the current GPSFix. */ private void updateGpsStatus() { - if(mGpsFixView == null && mGpsFix == null){ + if (mGpsFixView == null && mGpsFix == null) { return; } diff --git a/org.envirocar.app/src/org/envirocar/app/view/logbook/LogbookAddFuelingFragment.java b/org.envirocar.app/src/org/envirocar/app/view/logbook/LogbookAddFuelingFragment.java index de0841d45..ee3298eb8 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/logbook/LogbookAddFuelingFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/logbook/LogbookAddFuelingFragment.java @@ -58,7 +58,6 @@ import butterknife.ButterKnife; import butterknife.InjectView; -import rx.Scheduler; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; @@ -109,7 +108,6 @@ public class LogbookAddFuelingFragment extends BaseInjectorFragment { @Inject protected DAOProvider daoProvider; - private Scheduler.Worker backgroundWorker = Schedulers.io().createWorker(); private CompositeSubscription subscriptions = new CompositeSubscription(); @Nullable diff --git a/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionActivity.java b/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionActivity.java index 83c2ec38b..956596d5f 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionActivity.java +++ b/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionActivity.java @@ -36,6 +36,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; import javax.inject.Inject; @@ -155,4 +157,9 @@ private void setSwitchState(float value) { e.printStackTrace(); } } + + @Override + public List getInjectionModules() { + return Arrays.asList(new OBDSelectionModule()); + } } diff --git a/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionFragment.java b/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionFragment.java index bd94a9627..e1284010b 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionFragment.java @@ -20,7 +20,6 @@ import android.app.AlertDialog; import android.bluetooth.BluetoothDevice; -import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.Toolbar; @@ -52,9 +51,12 @@ import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action0; import rx.schedulers.Schedulers; /** + * TODO JavaDoc + * * @author dewall */ public class OBDSelectionFragment extends BaseInjectorFragment { @@ -88,11 +90,11 @@ public interface ShowSnackbarListener { private OBDDeviceListAdapter mNewDevicesArrayAdapter; private OBDDeviceListAdapter mPairedDevicesAdapter; + private Subscription mBTDiscoverySubscription; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // infalte the content view of this activity. View contentView = inflater.inflate(R.layout.activity_obd_selection_fragment, container, false); @@ -116,6 +118,15 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle return contentView; } + @Override + public void onDestroy() { + if (mBTDiscoverySubscription != null && !mBTDiscoverySubscription.isUnsubscribed()) { + mBTDiscoverySubscription.unsubscribe(); + } + + super.onDestroy(); + } + @Subscribe public void onBluetoothStateChangedEvent(BluetoothStateChangedEvent event) { LOGGER.debug("onBluetoothStateChangedEvent(): " + event.toString()); @@ -158,10 +169,16 @@ private void startBluetoothDiscovery() { // the current adapter. mNewDevicesArrayAdapter.clear(); - // Subscription sub = mBluetoothHandler.startBluetoothDeviceDiscoveryObservable(true) - Subscription sub = mBluetoothHandler.startBluetoothDiscoveryOnlyUnpaired() + mBTDiscoverySubscription = mBluetoothHandler.startBluetoothDiscoveryOnlyUnpaired() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + LOGGER.info("Canceling bluetooth device discovery"); + mBluetoothHandler.stopBluetoothDeviceDiscovery(); + } + }) .subscribe( new Subscriber() { @Override @@ -278,13 +295,10 @@ public void onDeleteOBDDevice(BluetoothDevice device) { new AlertDialog.Builder(getActivity()) .setView(contentView) .setPositiveButton(R.string.obd_selection_dialog_pairing_title, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // If this button is clicked, pair with the given device - view1.setClickable(false); - pairDevice(device, view1); - } + (dialog, which) -> { + // If this button is clicked, pair with the given device + view1.setClickable(false); + pairDevice(device, view1); }) .setNegativeButton(R.string.cancel, null) // Nothing to do on cancel .create() diff --git a/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionModule.java b/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionModule.java new file mode 100644 index 000000000..7b9e6cfc5 --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/view/obdselection/OBDSelectionModule.java @@ -0,0 +1,21 @@ +package org.envirocar.app.view.obdselection; + + +import org.envirocar.app.BaseApplicationModule; + +import dagger.Module; + + +/** + * TODO JavaDoc + * + * @author dewall + */ +@Module( + injects = {OBDSelectionFragment.class}, + addsTo = BaseApplicationModule.class, + library = true, + complete = false +) +public class OBDSelectionModule { +} diff --git a/org.envirocar.app/src/org/envirocar/app/view/preferences/Tempomat.java b/org.envirocar.app/src/org/envirocar/app/view/preferences/Tempomat.java index 5e0d96f17..d16b3994a 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/preferences/Tempomat.java +++ b/org.envirocar.app/src/org/envirocar/app/view/preferences/Tempomat.java @@ -208,8 +208,8 @@ protected void onDraw(Canvas canvas) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - LOGGER.info(String.format("onMeasure(%s,%s)", "" + widthMeasureSpec, "" + - heightMeasureSpec)); +// LOGGER.info(String.format("onMeasure(%s,%s)", "" + widthMeasureSpec, "" + +// heightMeasureSpec)); // Get the size and mode of width and height. int widthSize = MeasureSpec.getSize(widthMeasureSpec); @@ -616,8 +616,8 @@ private void init() { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - LOGGER.info(String.format("SpeedIndicator.onMeasure(%s,%s)", "" + widthMeasureSpec, "" + - heightMeasureSpec)); +// LOGGER.info(String.format("SpeedIndicator.onMeasure(%s,%s)", "" + widthMeasureSpec, "" + +// heightMeasureSpec)); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); diff --git a/org.envirocar.app/src/org/envirocar/app/view/settings/GeneralSettings.java b/org.envirocar.app/src/org/envirocar/app/view/settings/GeneralSettingsFragment.java similarity index 92% rename from org.envirocar.app/src/org/envirocar/app/view/settings/GeneralSettings.java rename to org.envirocar.app/src/org/envirocar/app/view/settings/GeneralSettingsFragment.java index 58e007d3a..c534b19aa 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/settings/GeneralSettings.java +++ b/org.envirocar.app/src/org/envirocar/app/view/settings/GeneralSettingsFragment.java @@ -1,55 +1,53 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.view.settings; - -import android.content.Context; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.preference.PreferenceFragment; -import android.view.View; - -import org.envirocar.app.R; - -/** - * @author dewall - */ -public class GeneralSettings extends PreferenceFragment { - public static final String KEY_PREFERENCE = "KEY_PREF_RESSOURCE"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Get the resource id from the attached bundle - Bundle bundle = this.getArguments(); - int resource = bundle.getInt(KEY_PREFERENCE, -1); - - if(resource != -1) { - addPreferencesFromResource(resource); - } - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - // set a non-transparent white background - view.setBackgroundColor(getResources().getColor(R.color.white_cario)); - } -} +/** + * Copyright (C) 2013 - 2015 the enviroCar community + * + * This file is part of the enviroCar app. + * + * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ +package org.envirocar.app.view.settings; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import android.view.View; + +import org.envirocar.app.R; + +/** + * @author dewall + */ +public class GeneralSettingsFragment extends PreferenceFragment { + public static final String KEY_PREFERENCE = "KEY_PREF_RESSOURCE"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Get the resource id from the attached bundle + Bundle bundle = this.getArguments(); + int resource = bundle.getInt(KEY_PREFERENCE, -1); + + if(resource != -1) { + addPreferencesFromResource(resource); + } + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // set a non-transparent white background + view.setBackgroundColor(getResources().getColor(R.color.white_cario)); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/view/settings/OBDSettingsFragment.java b/org.envirocar.app/src/org/envirocar/app/view/settings/OBDSettingsFragment.java index eeb41142b..72314ce61 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/settings/OBDSettingsFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/settings/OBDSettingsFragment.java @@ -43,6 +43,8 @@ import javax.inject.Inject; /** + * TODO JavaDoc + * * @author dewall */ public class OBDSettingsFragment extends PreferenceFragment { diff --git a/org.envirocar.app/src/org/envirocar/app/view/settings/OtherSettingsFragment.java b/org.envirocar.app/src/org/envirocar/app/view/settings/OtherSettingsFragment.java index 2bb0133d9..45397b20c 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/settings/OtherSettingsFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/settings/OtherSettingsFragment.java @@ -32,6 +32,8 @@ import org.envirocar.core.util.Util; /** + * TODO JavaDoc + * * @author dewall */ public class OtherSettingsFragment extends PreferenceFragment { diff --git a/org.envirocar.app/src/org/envirocar/app/view/settings/NewSettingsActivity.java b/org.envirocar.app/src/org/envirocar/app/view/settings/SettingsActivity.java similarity index 90% rename from org.envirocar.app/src/org/envirocar/app/view/settings/NewSettingsActivity.java rename to org.envirocar.app/src/org/envirocar/app/view/settings/SettingsActivity.java index 79b687088..d4438b2ba 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/settings/NewSettingsActivity.java +++ b/org.envirocar.app/src/org/envirocar/app/view/settings/SettingsActivity.java @@ -1,154 +1,164 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.view.settings; - -import android.app.Fragment; -import android.os.Bundle; -import android.support.design.widget.Snackbar; -import android.support.v7.widget.Toolbar; -import android.view.MenuItem; -import android.view.View; - -import org.envirocar.app.R; -import org.envirocar.core.injection.BaseInjectorActivity; - -import butterknife.ButterKnife; -import butterknife.InjectView; -import butterknife.OnClick; - -/** - * @author dewall - */ -public class NewSettingsActivity extends BaseInjectorActivity { - - @InjectView(R.id.fragment_settings_main_toolbar) - protected Toolbar mToolbar; - - @InjectView(R.id.fragment_settings_main_general_settings) - protected View mGeneralSettingsLayout; - @InjectView(R.id.fragment_settings_main_obd_settings) - protected View mOBDSettingsLayout; - @InjectView(R.id.fragment_settings_main_car_settings) - protected View mCarSettingsLayout; - @InjectView(R.id.fragment_settings_main_optional_settings) - protected View mOptionalSettingsLayout; - @InjectView(R.id.fragment_settings_main_other_settings) - protected View mOtherSettingsLayout; - - private Fragment mCurrentVisibleFragment; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.fragment_settings_main); - ButterKnife.inject(this); - - setSupportActionBar(mToolbar); - // Enables the home button. - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // just do the same as on back pressed. - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onBackPressed() { - if (mCurrentVisibleFragment != null) { - getFragmentManager() - .beginTransaction() - .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right) - .remove(mCurrentVisibleFragment) - .commit(); - mCurrentVisibleFragment = null; - } else { - super.onBackPressed(); - } - } - - /** - * Called when the general settings layout is clicked. It creates and opens the general - * settings fragment. - */ - @OnClick(R.id.fragment_settings_main_general_settings) - protected void onClickGeneralSettings() { - createAndShowSettingsFragment(R.xml.preferences_general); - } - - /** - * Called when the OBD settings layout is clicked. It creates a new {@link - * OBDSettingsFragment} and opens this in the settings container. - */ - @OnClick(R.id.fragment_settings_main_obd_settings) - protected void onClickOBDSettings() { - showFragment(new OBDSettingsFragment()); - } - - @OnClick(R.id.fragment_settings_main_car_settings) - protected void onClickCarSettings() { - Snackbar.make(mToolbar, "Not implemented yet!", Snackbar.LENGTH_LONG).show(); - } - - @OnClick(R.id.fragment_settings_main_optional_settings) - protected void onClickOptionalSettings() { - createAndShowSettingsFragment(R.xml.preferences_optional); - } - - @OnClick(R.id.fragment_settings_main_other_settings) - protected void onClickOtherSettings() { - showFragment(new OtherSettingsFragment()); - } - - /** - * @param resource - */ - private void createAndShowSettingsFragment(int resource) { - Fragment settings = new GeneralSettings(); - - // Set the preferences xml in the generated bundle. - Bundle bundle = new Bundle(); - bundle.putInt(GeneralSettings.KEY_PREFERENCE, resource); - settings.setArguments(bundle); - - // show the Settings fragment. - showFragment(settings); - } - - /** - * @param fragment - */ - private void showFragment(Fragment fragment) { - // Set the fragment into the main container of the view. - getFragmentManager() - .beginTransaction() - // Set some animations. - .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right) - .replace(R.id.fragment_settings_main_container, fragment) - .commit(); - mCurrentVisibleFragment = fragment; - } -} +/** + * Copyright (C) 2013 - 2015 the enviroCar community + * + * This file is part of the enviroCar app. + * + * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ +package org.envirocar.app.view.settings; + +import android.app.Fragment; +import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; + +import org.envirocar.app.R; +import org.envirocar.core.injection.BaseInjectorActivity; + +import java.util.Arrays; +import java.util.List; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import butterknife.OnClick; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class SettingsActivity extends BaseInjectorActivity { + + @InjectView(R.id.fragment_settings_main_toolbar) + protected Toolbar mToolbar; + + @InjectView(R.id.fragment_settings_main_general_settings) + protected View mGeneralSettingsLayout; + @InjectView(R.id.fragment_settings_main_obd_settings) + protected View mOBDSettingsLayout; + @InjectView(R.id.fragment_settings_main_car_settings) + protected View mCarSettingsLayout; + @InjectView(R.id.fragment_settings_main_optional_settings) + protected View mOptionalSettingsLayout; + @InjectView(R.id.fragment_settings_main_other_settings) + protected View mOtherSettingsLayout; + + private Fragment mCurrentVisibleFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.fragment_settings_main); + ButterKnife.inject(this); + + setSupportActionBar(mToolbar); + // Enables the home button. + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // just do the same as on back pressed. + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + if (mCurrentVisibleFragment != null) { + getFragmentManager() + .beginTransaction() + .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right) + .remove(mCurrentVisibleFragment) + .commit(); + mCurrentVisibleFragment = null; + } else { + super.onBackPressed(); + } + } + + /** + * Called when the general settings layout is clicked. It creates and opens the general + * settings fragment. + */ + @OnClick(R.id.fragment_settings_main_general_settings) + protected void onClickGeneralSettings() { + createAndShowSettingsFragment(R.xml.preferences_general); + } + + /** + * Called when the OBD settings layout is clicked. It creates a new {@link + * OBDSettingsFragment} and opens this in the settings container. + */ + @OnClick(R.id.fragment_settings_main_obd_settings) + protected void onClickOBDSettings() { + showFragment(new OBDSettingsFragment()); + } + + @OnClick(R.id.fragment_settings_main_car_settings) + protected void onClickCarSettings() { + Snackbar.make(mToolbar, "Not implemented yet!", Snackbar.LENGTH_LONG).show(); + } + + @OnClick(R.id.fragment_settings_main_optional_settings) + protected void onClickOptionalSettings() { + createAndShowSettingsFragment(R.xml.preferences_optional); + } + + @OnClick(R.id.fragment_settings_main_other_settings) + protected void onClickOtherSettings() { + showFragment(new OtherSettingsFragment()); + } + + /** + * @param resource + */ + private void createAndShowSettingsFragment(int resource) { + Fragment settings = new GeneralSettingsFragment(); + + // Set the preferences xml in the generated bundle. + Bundle bundle = new Bundle(); + bundle.putInt(GeneralSettingsFragment.KEY_PREFERENCE, resource); + settings.setArguments(bundle); + + // show the Settings fragment. + showFragment(settings); + } + + /** + * @param fragment + */ + private void showFragment(Fragment fragment) { + // Set the fragment into the main container of the view. + getFragmentManager() + .beginTransaction() + // Set some animations. + .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right) + .replace(R.id.fragment_settings_main_container, fragment) + .commit(); + mCurrentVisibleFragment = fragment; + } + + @Override + public List getInjectionModules() { + return Arrays.asList(new SettingsModule()); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/view/settings/SettingsModule.java b/org.envirocar.app/src/org/envirocar/app/view/settings/SettingsModule.java new file mode 100644 index 000000000..f189cc9b9 --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/view/settings/SettingsModule.java @@ -0,0 +1,23 @@ +package org.envirocar.app.view.settings; + +import org.envirocar.app.BaseApplicationModule; + +import dagger.Module; + +/** + * TODO JavaDoc + * + * @author dewall + */ +@Module( + injects = { + GeneralSettingsFragment.class, + OBDSettingsFragment.class, + OtherSettingsFragment.class + }, + addsTo = BaseApplicationModule.class, + library = true, + complete = false +) +public class SettingsModule { +} diff --git a/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackDetailsActivity.java b/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackDetailsActivity.java index 9fcf1554c..d9fc5432f 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackDetailsActivity.java +++ b/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackDetailsActivity.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -40,7 +40,9 @@ import com.mapbox.mapboxsdk.views.MapView; import org.envirocar.app.R; +import org.envirocar.app.handler.PreferencesHandler; import org.envirocar.app.view.utils.MapUtils; +import org.envirocar.core.entity.Car; import org.envirocar.core.entity.Track; import org.envirocar.core.exception.FuelConsumptionException; import org.envirocar.core.exception.NoMeasurementsException; @@ -48,6 +50,7 @@ import org.envirocar.core.injection.BaseInjectorActivity; import org.envirocar.core.logging.Logger; import org.envirocar.core.trackprocessing.TrackStatisticsProvider; +import org.envirocar.core.utils.CarUtils; import org.envirocar.storage.EnviroCarDB; import java.text.DateFormat; @@ -261,18 +264,30 @@ private void initViewValues(Track track) { mDurationText.setText(text); mDescriptionText.setText(track.getDescription()); - mCarText.setText(track.getCar().toString()); + mCarText.setText(CarUtils.carToStringWithLinebreak(track.getCar())); mBeginText.setText(DATE_FORMAT.format(new Date(track.getStartTime()))); mEndText.setText(DATE_FORMAT.format(new Date(track.getEndTime()))); - mEmissionText.setText(DECIMAL_FORMATTER_TWO_DIGITS.format( - ((TrackStatisticsProvider) track).getGramsPerKm()) + " g/km"); - mConsumptionText.setText( - String.format("%s l/h, %s l/100km", - DECIMAL_FORMATTER_TWO_DIGITS.format( - ((TrackStatisticsProvider) track).getFuelConsumptionPerHour()), - DECIMAL_FORMATTER_TWO_DIGITS.format( - ((TrackStatisticsProvider) track).getLiterPerHundredKm()))); + // show consumption and emission either when the fuel type of the track's car is + // gasoline or the beta setting has been enabled. + if (track.getCar().getFuelType() == Car.FuelType.GASOLINE || + PreferencesHandler.isDieselConsumptionEnabled(this)) { + mEmissionText.setText(DECIMAL_FORMATTER_TWO_DIGITS.format( + ((TrackStatisticsProvider) track).getGramsPerKm()) + " g/km"); + mConsumptionText.setText( + String.format("%s l/h\n%s l/100 km", + DECIMAL_FORMATTER_TWO_DIGITS.format( + ((TrackStatisticsProvider) track) + .getFuelConsumptionPerHour()), + + DECIMAL_FORMATTER_TWO_DIGITS.format( + ((TrackStatisticsProvider) track).getLiterPerHundredKm()))); + } else { + mEmissionText.setText(R.string.track_list_details_diesel_not_supported); + mConsumptionText.setText(R.string.track_list_details_diesel_not_supported); + mEmissionText.setTextColor(Color.RED); + mConsumptionText.setTextColor(Color.RED); + } } catch (FuelConsumptionException e) { e.printStackTrace(); } catch (NoMeasurementsException e) { diff --git a/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackSpeedMapOverlay.java b/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackSpeedMapOverlay.java index ec0cb7541..81573087e 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackSpeedMapOverlay.java +++ b/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackSpeedMapOverlay.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -82,144 +82,124 @@ public TrackSpeedMapOverlay(Track track) { initPath(); setOverlayIndex(1); - } - // @Override - // public void addPoint(double aLatitude, double aLongitude) { - // mPoints.add(new PointF((float) aLatitude, (float) aLongitude)); - // } - // - // @Override - // public int getNumberOfPoints() { - // return mPoints.size(); - // } - // - // @Override - // protected void draw(Canvas canvas, MapView mapView, boolean shadow) { - // final int size = this.mPoints.size(); - // - // // nothing to paint - // if (shadow || size < 2) { - // return; - // } - // - // final Projection pj = mapView.getProjection(); - // - // // precompute new points to the intermediate projection. - // for (; this.mPointsPrecomputed < size; this.mPointsPrecomputed++) { - // final PointF pt = this.mPoints.get(this.mPointsPrecomputed); - // pj.toMapPixelsProjected((double) pt.x, (double) pt.y, pt); - // } - // - // PointF screenPoint0 = null; // points on screen - // PointF screenPoint1; - // PointF projectedPoint0; // points from the points list - // PointF projectedPoint1; - // - // // clipping rectangle in the intermediate projection, to avoid performing projection. - // final Rect clipBounds = pj.fromPixelsToProjected(pj.getScreenRect()); - // - // mPath.rewind(); - // boolean needsDrawing = !mOptimizePath; - // projectedPoint0 = this.mPoints.get(size - 1); - // mLineBounds.set((int) projectedPoint0.x, (int) projectedPoint0.y, (int) - // projectedPoint0.x, - // (int) projectedPoint0.y); - // - // for (int i = size - 2; i >= 0; i--) { - // // compute next points - // projectedPoint1 = this.mPoints.get(i); - // - // //mLineBounds needs to be computed - // mLineBounds.union((int) projectedPoint1.x, (int) projectedPoint1.y); - // - // if (mOptimizePath && !Rect.intersects(clipBounds, mLineBounds)) { - // // skip this line, move to next point - // projectedPoint0 = projectedPoint1; - // mLineBounds.set((int) projectedPoint0.x, (int) projectedPoint0.y, (int) - // projectedPoint0.x, - // (int) projectedPoint0.y); - // screenPoint0 = null; - // continue; - // } - // - // // the starting point may be not calculated, because previous segment was out - // of clip - // // bounds - // if (screenPoint0 == null) { - // screenPoint0 = pj.toMapPixelsTranslated(projectedPoint0, this.mTempPoint1); - // mPath.moveTo(screenPoint0.x, screenPoint0.y); - // } - // - // screenPoint1 = pj.toMapPixelsTranslated(projectedPoint1, this.mTempPoint2); - // - // // skip this point, too close to previous point - // if (Math.abs(screenPoint1.x - screenPoint0.x) + Math.abs( - // screenPoint1.y - screenPoint0.y) <= 1) { - // continue; - // } - // - // Path segment = new Path(); - // segment.moveTo(screenPoint0.x, screenPoint0.y); - // segment.lineTo(screenPoint1.x, screenPoint1.y); - // mPaths.add(segment); - // - // Paint paint = new Paint(); - // paint.setColor(getColor(mValues.get(i))); - // paint.setStyle(Paint.Style.STROKE); - // paint.setStrokeWidth(5); - // mPaints.add(paint); - // - // - // mPath.lineTo(screenPoint1.x, screenPoint1.y); - // // update starting point to next position - // projectedPoint0 = projectedPoint1; - // screenPoint0.x = screenPoint1.x; - // screenPoint0.y = screenPoint1.y; - // if (mOptimizePath) { - // needsDrawing = true; - // mLineBounds.set((int) projectedPoint0.x, (int) projectedPoint0.y, (int) - // projectedPoint0.x, - // (int) projectedPoint0.y); - // } - // } - // if (!mOptimizePath) { - // needsDrawing = Rect.intersects(clipBounds, mLineBounds); - // } - // - // if (needsDrawing) { - //// final float realWidth = this.mPaint.getStrokeWidth(); - //// this.mPaint.setStrokeWidth(realWidth / mapView.getScale()); - //// canvas.drawPath(mPath, this.mPaint); - //// this.mPaint.setStrokeWidth(realWidth); - // - // for(int i = 0, s = mPaths.size(); i < s; i++){ - // Path path = mPaths.get(i); - // Paint p = mPaints.get(i); - // float w = p.getStrokeWidth(); - // p.setStrokeWidth(w / mapView.getScale()); - // canvas.drawPath(path,p); - // p.setStrokeWidth(w); - // } - // } - // } - - private int getColor(Double value) { - if (value == null) { - return Color.BLACK; - } - if (value < 50.0) { - return Color.GREEN; - } - return Color.RED; - } +// @Override +// public void addPoint(double aLatitude, double aLongitude) { +// mPoints.add(new PointF((float) aLatitude, (float) aLongitude)); +// } +// +// @Override +// public int getNumberOfPoints() { +// return mPoints.size(); +// } +// +// @Override +// protected void draw(Canvas canvas, MapView mapView, boolean shadow) { +// final int size = this.mPoints.size(); +// +// // nothing to paint +// if (shadow || size < 2) { +// return; +// } +// +// final Projection pj = mapView.getProjection(); +// +// // precompute new points to the intermediate projection. +// for (; this.mPointsPrecomputed < size; this.mPointsPrecomputed++) { +// final PointF pt = this.mPoints.get(this.mPointsPrecomputed); +// pj.toMapPixelsProjected((double) pt.x, (double) pt.y, pt); +// +// Paint paint = new Paint(); +// paint.setColor(getColor(mValues.get(this.mPointsPrecomputed))); +// paint.setStyle(Paint.Style.STROKE); +// paint.setStrokeWidth(5); +// mPaints.add(paint); +// } +// +// PointF screenPoint0 = null; // points on screen +// PointF screenPoint1; +// PointF projectedPoint0; // points from the points list +// PointF projectedPoint1; +// +// // clipping rectangle in the intermediate projection, to avoid performing projection. +// final Rect clipBounds = pj.fromPixelsToProjected(pj.getScreenRect()); +// +// mPath.rewind(); +// boolean needsDrawing = !mOptimizePath; +// projectedPoint0 = this.mPoints.get(size - 1); +// mLineBounds.set((int) projectedPoint0.x, (int) projectedPoint0.y, +// (int) projectedPoint0.x, (int) projectedPoint0.y); +// +// mPaths.clear(); +// +// for (int i = size - 2; i >= 0; i--) { +// // compute next points +// projectedPoint1 = this.mPoints.get(i); +// +// //mLineBounds needs to be computed +// mLineBounds.union((int) projectedPoint1.x, (int) projectedPoint1.y); +// +// // the starting point may be not calculated, because previous segment was out +// // of clip bounds +// if (screenPoint0 == null) { +// screenPoint0 = pj.toMapPixelsTranslated(projectedPoint0, this.mTempPoint1); +// } +// +// screenPoint1 = pj.toMapPixelsTranslated(projectedPoint1, this.mTempPoint2); +// +// // skip this point, too close to previous point +// if (Math.abs(screenPoint1.x - screenPoint0.x) + +// Math.abs(screenPoint1.y - screenPoint0.y) <= 1) { +// continue; +// } +// +// Path segment = new Path(); +// segment.moveTo(screenPoint0.x, screenPoint0.y); +// segment.lineTo(screenPoint1.x, screenPoint1.y); +// mPaths.add(segment); +// +// // update starting point to next position +// projectedPoint0 = projectedPoint1; +// screenPoint0.x = screenPoint1.x; +// screenPoint0.y = screenPoint1.y; +// +// if (mOptimizePath) { +// needsDrawing = true; +// mLineBounds.set((int) projectedPoint0.x, (int) projectedPoint0.y, +// (int) projectedPoint0.x, (int) projectedPoint0.y); +// } +// } +// +// if (needsDrawing) { +// for (int i = 0, s = mPaths.size(); i < s; i++) { +// Path path = mPaths.get(i); +// Paint p = mPaints.get(i); +// float w = p.getStrokeWidth(); +// p.setStrokeWidth(w / mapView.getScale()); +// canvas.drawPath(path, p); +// p.setStrokeWidth(w); +// } +// } +// } +// +// private int getColor(Double value) { +// if (value == null) { +// return Color.BLACK; +// } +// ArgbEvaluator ev = new ArgbEvaluator(); +// return (int) ev.evaluate((float) (value/80f), Color.RED, Color.GREEN); +//// if (value < 50.0) { +//// return Color.GREEN; +//// } +//// return Color.RED; +// } /** * Initializes the track path and the bounding boxes required by the mapviews. */ private void initPath() { - mPoints = new ArrayList(); + mPoints = new ArrayList<>(); mValues = new ArrayList<>(); List measurementList = mTrack.getMeasurements(); diff --git a/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackStatisticsActivity.java b/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackStatisticsActivity.java index d2a0ff36c..b0fae8a0b 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackStatisticsActivity.java +++ b/org.envirocar.app/src/org/envirocar/app/view/trackdetails/TrackStatisticsActivity.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -22,6 +22,7 @@ import android.app.Activity; import android.app.Fragment; import android.content.Intent; +import android.location.Location; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.Toolbar; @@ -32,10 +33,10 @@ import android.view.ViewGroup; import org.envirocar.app.R; -import org.envirocar.app.storage.DbAdapter; import org.envirocar.core.entity.Measurement; import org.envirocar.core.entity.Track; import org.envirocar.core.injection.BaseInjectorActivity; +import org.envirocar.storage.EnviroCarDB; import java.util.ArrayList; import java.util.List; @@ -44,6 +45,7 @@ import butterknife.ButterKnife; import butterknife.InjectView; +import lecho.lib.hellocharts.formatter.SimpleAxisValueFormatter; import lecho.lib.hellocharts.gesture.ZoomType; import lecho.lib.hellocharts.listener.DummyVieportChangeListener; import lecho.lib.hellocharts.model.Axis; @@ -54,8 +56,12 @@ import lecho.lib.hellocharts.util.ChartUtils; import lecho.lib.hellocharts.view.LineChartView; import lecho.lib.hellocharts.view.PreviewLineChartView; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** + * TODO JavaDoc + * * @author dewall */ public class TrackStatisticsActivity extends BaseInjectorActivity { @@ -68,12 +74,12 @@ public static void createInstance(Activity activity, int trackID) { } @Inject - protected DbAdapter mDBAdapter; + protected EnviroCarDB enviroCarDB; + @InjectView(R.id.activity_track_statistics_toolbar) protected Toolbar mToolbar; private Track mTrack; - private PlaceholderFragment mPlaceholderFragment; @Override @@ -83,32 +89,30 @@ protected void onCreate(Bundle savedInstanceState) { int trackID = getIntent().getIntExtra(EXTRA_TRACKID, -1); Track.TrackId trackid = new Track.TrackId(trackID); - mTrack = mDBAdapter.getTrack(trackid); + + enviroCarDB.getTrack(trackid) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(track -> { + mTrack = track; + if (savedInstanceState == null) { + mPlaceholderFragment = new PlaceholderFragment(mTrack); + getFragmentManager().beginTransaction() + .add(R.id.activity_track_statistics_layout_container, + mPlaceholderFragment).commit(); + } + + inflateMenuProperties(track); + }); // Inject all annotated views. ButterKnife.inject(this); // Initializes the Toolbar. setSupportActionBar(mToolbar); - getSupportActionBar().setTitle("Track Statistics"); + getSupportActionBar().setTitle(R.string.track_statistics); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - if (savedInstanceState == null) { - mPlaceholderFragment = new PlaceholderFragment(mTrack); - getFragmentManager().beginTransaction() - .add(R.id.activity_track_statistics_layout_container, - mPlaceholderFragment).commit(); - } - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - for(Measurement.PropertyKey key : Measurement.PropertyKey.values()){ - menu.add(key.toString()); - } - return super.onCreateOptionsMenu(menu); } @Override @@ -118,8 +122,8 @@ public boolean onOptionsItemSelected(MenuItem item) { finish(); } - for(Measurement.PropertyKey key : Measurement.PropertyKey.values()){ - if(key.toString().equals(item.getTitle())){ + for (Measurement.PropertyKey key : Measurement.PropertyKey.values()) { + if (getString(key.getStringResource()).equals(item.getTitle())) { mPlaceholderFragment.generateData(key); break; } @@ -127,6 +131,17 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + private void inflateMenuProperties(Track track) { + Menu menu = mToolbar.getMenu(); + if (mTrack != null && !mTrack.getMeasurements().isEmpty()) { + for (Measurement.PropertyKey key : mTrack.getSupportedProperties()) { + menu.add(key.getStringResource()); + } + } + + mToolbar.invalidate(); + } + public static class PlaceholderFragment extends Fragment { @InjectView(R.id.activity_track_statistics_fragment_chart) @@ -175,31 +190,32 @@ public void onViewportChanged(Viewport viewport) { } private void generateData(Measurement.PropertyKey propertyKey) { - List values = new ArrayList(); - - List measurements = mTrack.getMeasurements(); - - for (int i = 0; i < measurements.size(); i++) { - Measurement m = measurements.get(i); - if (m != null && m.hasProperty(propertyKey)) { - values.add(new PointValue(i, m.getProperty(propertyKey).floatValue())); - } - } + // Generate the PointValues for the Graph. + List values = generateDistancedBasedData(propertyKey, mTrack); Line line = new Line(values); line.setColor(getResources().getColor(R.color.green_dark_cario)); line.setHasPoints(false); - List lines = new ArrayList(); + List lines = new ArrayList<>(); lines.add(line); mChartData = new LineChartData(lines); mChartData.setAxisXBottom(new Axis()); mChartData.setAxisYLeft(new Axis().setHasLines(true)); + setDistanceAxis(mChartData); + setYAxis(propertyKey, mChartData); mPreviewChartData = new LineChartData(mChartData); mPreviewChartData.getLines().get(0).setColor(ChartUtils.DEFAULT_DARKEN_COLOR); + Axis axisXBottom = mPreviewChartData.getAxisXBottom(); + axisXBottom.setHasSeparationLine(false); + axisXBottom.setHasTiltedLabels(true); + axisXBottom.setTextColor(ChartUtils.DEFAULT_DARKEN_COLOR); + + mPreviewChartData.getAxisYLeft().setTextColor(ChartUtils.DEFAULT_DARKEN_COLOR); + // Set the data in the charts. mChart.setLineChartData(mChartData); mPreviewChart.setLineChartData(mPreviewChartData); @@ -208,9 +224,61 @@ private void generateData(Measurement.PropertyKey propertyKey) { previewX(); } - private void previewX(){ + private List generateDistancedBasedData(Measurement.PropertyKey propertyKey, Track track){ + List values = new ArrayList(); + + // temporary array for computing distances. + float[] tmp = new float[1]; + float distance = 0; + + // temporary value for the last measurement + Measurement lastMeasurement = null; + + for(Measurement m : track.getMeasurements()){ + if(lastMeasurement != null){ + Location.distanceBetween(lastMeasurement.getLatitude(), lastMeasurement + .getLongitude(), m.getLatitude(), m.getLongitude(), tmp); + distance += tmp[0] / 1000f; // we need km not meters. + } + if (m != null && m.hasProperty(propertyKey)) { + values.add(new PointValue(distance, m.getProperty(propertyKey).floatValue())); + } + lastMeasurement = m; + } + + return values; + } + + private void setDistanceAxis(LineChartData data) { + Axis distAxis = new Axis(); + distAxis.setName(getString(R.string.track_statistics_distance)); + distAxis.setTextColor(getResources().getColor(R.color.blue_dark_cario)); + distAxis.setMaxLabelChars(5); + distAxis.setFormatter(new SimpleAxisValueFormatter() + .setAppendedText("km".toCharArray())); + distAxis.setHasLines(true); + distAxis.setHasTiltedLabels(true); + distAxis.setTextSize(10); + distAxis.setHasSeparationLine(false); + data.setAxisXBottom(distAxis); + } + + private void setYAxis(Measurement.PropertyKey key, LineChartData data){ + Axis yAxis = new Axis(); + yAxis.setName(getString(key.getStringResource())); + yAxis.setTextColor(getResources().getColor(R.color.blue_dark_cario)); + yAxis.setMaxLabelChars(3); + yAxis.setHasLines(true); + yAxis.setTextSize(10); + yAxis.setFormatter(new SimpleAxisValueFormatter()); + yAxis.setInside(false); + yAxis.setHasSeparationLine(false); + data.setAxisYLeft(yAxis); + } + + private void previewX() { Viewport tempViewport = new Viewport(mChart.getMaximumViewport()); - float dx = tempViewport.width() / 4; + float dx = tempViewport.width() / 3; tempViewport.inset(dx, 0); mPreviewChart.setCurrentViewportWithAnimation(tempViewport); mPreviewChart.setZoomType(ZoomType.HORIZONTAL); @@ -223,30 +291,5 @@ private void previewY() { mPreviewChart.setCurrentViewportWithAnimation(tempViewport); mPreviewChart.setZoomType(ZoomType.VERTICAL); } - - private void generateDefaultData() { - int numValues = 50; - - List values = new ArrayList(); - for (int i = 0; i < numValues; ++i) { - values.add(new PointValue(i, (float) Math.random() * 100f)); - } - - Line line = new Line(values); - line.setColor(ChartUtils.COLOR_GREEN); - line.setHasPoints(false);// too many values so don't draw points. - - List lines = new ArrayList(); - lines.add(line); - - mChartData = new LineChartData(lines); - mChartData.setAxisXBottom(new Axis()); - mChartData.setAxisYLeft(new Axis().setHasLines(true)); - - // prepare preview data, is better to use separate deep copy for preview chart. - // Set color to grey to make preview area more visible. - mPreviewChartData = new LineChartData(mChartData); - mPreviewChartData.getLines().get(0).setColor(ChartUtils.DEFAULT_DARKEN_COLOR); - } } } diff --git a/org.envirocar.app/src/org/envirocar/app/view/tracklist/AbstractTrackListCardFragment.java b/org.envirocar.app/src/org/envirocar/app/view/tracklist/AbstractTrackListCardFragment.java index 7ae48d795..153b861de 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/tracklist/AbstractTrackListCardFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/tracklist/AbstractTrackListCardFragment.java @@ -36,8 +36,9 @@ import com.afollestad.materialdialogs.MaterialDialog; import org.envirocar.app.R; -import org.envirocar.app.TrackHandler; import org.envirocar.app.handler.TermsOfUseManager; +import org.envirocar.app.handler.TrackDAOHandler; +import org.envirocar.app.handler.TrackUploadHandler; import org.envirocar.app.handler.UserHandler; import org.envirocar.app.view.utils.ECAnimationUtils; import org.envirocar.core.entity.Track; @@ -59,6 +60,7 @@ import butterknife.ButterKnife; import butterknife.InjectView; +import rx.Observable; import rx.Scheduler; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; @@ -83,7 +85,9 @@ public abstract class AbstractTrackListCardFragment mEnvirocarDB.getTrack(track.getTrackID())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map(upToDateRef -> { // If the track is a local track, then delete and return whether it was // successful. - return upToDateRef.isLocalTrack() && mTrackHandler.deleteLocalTrack - (upToDateRef.getTrackID()); + return upToDateRef.isLocalTrack() && + mTrackDAOHandler.deleteLocalTrack(upToDateRef.getTrackID()); }) .subscribe(getDeleteTrackSubscriber(track)); } @@ -287,7 +292,6 @@ public void onStart() { public void onCompleted() { LOG.info(String.format("onCompleted() delete track -> [%s]", track.getName())); - } @Override diff --git a/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListLocalCardFragment.java b/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListLocalCardFragment.java index 722d0eb7e..2a1c4799f 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListLocalCardFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListLocalCardFragment.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -25,7 +25,6 @@ import com.afollestad.materialdialogs.MaterialDialog; import org.envirocar.app.R; -import org.envirocar.app.TrackHandler; import org.envirocar.app.view.trackdetails.TrackDetailsActivity; import org.envirocar.core.entity.Track; import org.envirocar.core.logging.Logger; @@ -41,6 +40,8 @@ import rx.schedulers.Schedulers; /** + * TODO JavaDoc + * * @author dewall */ public class TrackListLocalCardFragment extends AbstractTrackListCardFragment< @@ -55,48 +56,200 @@ interface OnTrackUploadedListener { } private OnTrackUploadedListener onTrackUploadedListener; - private Subscription subscription; - private void uploadTrack(Track track) { - mBackgroundWorker.schedule(() -> mTrackHandler.uploadTrack(getActivity(), track, - new TrackHandler - .TrackUploadCallback() { + private Subscription loadTracksSubscription; + private Subscription uploadTrackSubscription; + + private int localTrackSize = 0; + + + @Override + public void onResume() { + LOG.info("onResume()"); + super.onResume(); + +// mFAB.setOnClickListener(v -> new MaterialDialog.Builder(getContext()) +// .title(R.string.track_list_upload_all_tracks_title) +// .content(R.string.track_list_upload_all_tracks_content) +// .positiveText(R.string.ok) +// .negativeText(R.string.cancel) +// .onPositive((materialDialog, dialogAction) -> uploadAllLocalTracks()) +// .show()); + } + + private void uploadAllLocalTracks() { +// uploadTrackSubscription = mEnvirocarDB.getAllLocalTracks() +// .first() +// .subscribeOn(Schedulers.io()) +// .observeOn(Schedulers.io()) +// .subscribe(new Subscriber>() { +// private MaterialDialog dialog; +// +// @Override +// public void onStart() { +// super.onStart(); +// +// dialog = new MaterialDialog.Builder(getContext()) +// .title(R.string.track_list_upload_all_tracks_title) +// .cancelable(false) +// .negativeText(R.string.cancel) +// .progress(true, tracksToUpload) +// .onNegative((materialDialog1, dialogAction) -> unsubscribe()) +// .show(); +// } +// +// @Override +// public void onCompleted() { +// +// } +// +// @Override +// public void onError(Throwable e) { +// +// } +// +// @Override +// public void onNext(List tracks) { +// +// } +// }); + + uploadTrackSubscription = mEnvirocarDB.getAllLocalTracks() + .first() + .map(tracks -> { + LOG.info("Tracks : " + tracks.size()); + localTrackSize = tracks.size(); + return tracks; + }) + .flatMap(tracks -> mTrackUploadHandler.uploadMultipleTracks(tracks) +// .lift(new Observable.Operator() { +// @Override +// public Subscriber call(Subscriber subscriber) { +// return new Subscriber() { +// @Override +// public void onCompleted() { +// subscriber.onCompleted(); +// } +// +// @Override +// public void onError(Throwable e) { +// if(e instanceof NoMeasurementsException){ +// +// } else{ +// subscriber.onError(e); +// } +// } +// +// @Override +// public void onNext(R track) { +// subscriber.onNext(track); +// } +// }; +// } +// }) + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnUnsubscribe(() -> LOG.info("Upload has been canceled")) + .subscribe(new Subscriber() { + private MaterialDialog materialDialog; + private int tracksToUpload = localTrackSize; + private int currentTrack = 1; + + @Override + public void onStart() { + LOG.info("onStart(): Upload of all local tracks started."); + materialDialog = new MaterialDialog.Builder(getContext()) + .title(R.string.track_list_upload_all_tracks_title) + .cancelable(false) + .negativeText(R.string.cancel) + .progress(true, tracksToUpload) + .onNegative((materialDialog1, dialogAction) -> unsubscribe()) + .show(); + } + + @Override + public void onCompleted() { + LOG.info("onCompleted(): Upload of all local tracks finished."); + materialDialog.dismiss(); + + showSnackbar(String.format("%s tracks have been uploaded.", + localTrackSize)); + } + + @Override + public void onError(Throwable e) { + LOG.error(e.getMessage(), e); + materialDialog.dismiss(); + + showSnackbar(String.format("")); + } + + @Override + public void onNext(Track uploaded) { + LOG.info("onNext(): Track [%s] has been uploaded -> [%s]", + uploaded.getName(), uploaded.getRemoteID()); + + // Update the lists. + mRecyclerViewAdapter.removeItem(uploaded); + mRecyclerViewAdapter.notifyDataSetChanged(); + onTrackUploadedListener.onTrackUploaded(uploaded); + + updateDialogContent(); + } + + private void updateDialogContent() { + currentTrack++; +// materialDialog.setContent(String.format(getString(R.string +// .track_list_upload_all_tracks_content), +// currentTrack, tracksToUpload)); + materialDialog.incrementProgress(1); + } + }); + } - private MaterialDialog mProgressDialog; + private void uploadTrack(Track track) { + uploadTrackSubscription = mTrackUploadHandler + .uploadSingleTrack(track, getActivity()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Subscriber() { + private MaterialDialog progressDialog; @Override - public void onUploadStarted(Track track) { - mMainThreadWorker.schedule(() -> - mProgressDialog = new MaterialDialog.Builder(getActivity()) - .title(R.string.track_list_upload_track_uploading) - .content(R.string.track_list_upload_track_please_wait) - .progress(true, 0) - .show()); + public void onStart() { + progressDialog = new MaterialDialog.Builder(getActivity()) + .title(R.string.track_list_upload_track_uploading) + .content(R.string.track_list_upload_track_please_wait) + .progress(true, 0) + .show(); } @Override - public void onSuccessfulUpload(Track track) { - if (mProgressDialog != null) mProgressDialog.dismiss(); + public void onCompleted() { + if (progressDialog != null) progressDialog.dismiss(); showSnackbar(String.format( getString(R.string.track_list_upload_track_success_template), track.getName())); + } - // Update the lists. - mMainThreadWorker.schedule(() -> { - mRecyclerViewAdapter.removeItem(track); - mRecyclerViewAdapter.notifyDataSetChanged(); - }); - - onTrackUploadedListener.onTrackUploaded(track); + @Override + public void onError(Throwable e) { + LOG.error(e.getMessage(), e); + if (progressDialog != null) + progressDialog.dismiss(); + showSnackbar(e.getMessage()); } @Override - public void onError(Track track, String message) { - if (mProgressDialog != null) - mProgressDialog.dismiss(); - showSnackbar(message); + public void onNext(Track track) { + // Update the lists. + mRecyclerViewAdapter.removeItem(track); + mRecyclerViewAdapter.notifyDataSetChanged(); + + onTrackUploadedListener.onTrackUploaded(track); } - })); + }); } @Override @@ -162,8 +315,12 @@ public void onDestroyView() { LOG.info("onDestroyView()"); super.onDestroyView(); - if (subscription != null && !subscription.isUnsubscribed()) { - subscription.unsubscribe(); + if (loadTracksSubscription != null && !loadTracksSubscription.isUnsubscribed()) { + loadTracksSubscription.unsubscribe(); + } + + if (uploadTrackSubscription != null && !uploadTrackSubscription.isUnsubscribed()) { + uploadTrackSubscription.unsubscribe(); } } @@ -183,7 +340,7 @@ protected Void doInBackground(Void... params) { } } - subscription = mEnvirocarDB.getAllLocalTracks() + loadTracksSubscription = mEnvirocarDB.getAllLocalTracks() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber>() { @@ -235,6 +392,9 @@ public void onNext(List tracks) { mRecyclerView.setVisibility(View.VISIBLE); infoView.setVisibility(View.GONE); mRecyclerViewAdapter.notifyDataSetChanged(); + +// ECAnimationUtils.animateShowView(getContext(), mFAB, +// R.anim.translate_slide_in_bottom_fragment); } else if (mTrackList.isEmpty()) { showText(R.drawable.img_tracks, R.string.track_list_bg_no_local_tracks, diff --git a/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListPagerFragment.java b/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListPagerFragment.java index 7e86c7086..876bf9e2c 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListPagerFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListPagerFragment.java @@ -58,7 +58,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle ButterKnife.inject(this, content); - trackListPageAdapter = new TrackListPagerAdapter(getFragmentManager()); + trackListPageAdapter = new TrackListPagerAdapter(getChildFragmentManager()); mViewPager.setAdapter(trackListPageAdapter); mTabLayout.setupWithViewPager(mViewPager); diff --git a/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListRemoteCardFragment.java b/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListRemoteCardFragment.java index 697db476e..f9727dcdf 100644 --- a/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListRemoteCardFragment.java +++ b/org.envirocar.app/src/org/envirocar/app/view/tracklist/TrackListRemoteCardFragment.java @@ -170,7 +170,7 @@ private void onDownloadTrackClickedInner(final Track track, AbstractTrackListCar holder.mProgressCircle.show(); track.setDownloadState(Track.DownloadState.DOWNLOADING); - mTrackHandler.fetchRemoteTrackObservable(track) + mTrackDAOHandler.fetchRemoteTrackObservable(track) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer() { @@ -353,8 +353,8 @@ private void updateView() { if (mTrackList.isEmpty()) { showText(R.drawable.img_tracks, - R.string.track_list_bg_no_local_tracks, - R.string.track_list_bg_no_local_tracks_sub); + R.string.track_list_bg_no_remote_tracks, + R.string.track_list_bg_no_remote_tracks_sub); } } diff --git a/org.envirocar.app/src/org/envirocar/app/views/ReactiveTermsOfUseDialog.java b/org.envirocar.app/src/org/envirocar/app/views/ReactiveTermsOfUseDialog.java new file mode 100644 index 000000000..2f7ae680c --- /dev/null +++ b/org.envirocar.app/src/org/envirocar/app/views/ReactiveTermsOfUseDialog.java @@ -0,0 +1,136 @@ +package org.envirocar.app.views; + + +import android.app.Activity; +import android.text.Html; +import android.text.Spanned; + +import com.afollestad.materialdialogs.MaterialDialog; + +import org.envirocar.app.R; +import org.envirocar.app.exception.NotAcceptedTermsOfUseException; +import org.envirocar.core.entity.TermsOfUse; +import org.envirocar.core.entity.User; +import org.envirocar.core.logging.Logger; + +import rx.Observable; +import rx.Scheduler; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action0; +import rx.schedulers.Schedulers; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class ReactiveTermsOfUseDialog { + private static Logger LOG = Logger.getLogger(ReactiveTermsOfUseDialog.class); + + private final Scheduler.Worker mainThreadWorker = AndroidSchedulers.mainThread() + .createWorker(); + private final Scheduler.Worker backgroundWorker = Schedulers.io().createWorker(); + + private final Activity activityContext; + private final User user; + private final TermsOfUse currentTermsOfUse; + + /** + * Constructor. + * + * @param activityContext + */ + public ReactiveTermsOfUseDialog(Activity activityContext, User user, TermsOfUse currentToU) { + this.activityContext = activityContext; + this.user = user; + this.currentTermsOfUse = currentToU; + } + + public Observable asObservable() { + LOG.info("asObservable()"); + return Observable.create(new Observable.OnSubscribe() { + private MaterialDialog termsOfUseDialog; + + @Override + public void call(Subscriber subscriber) { + LOG.info("asObservable().call()"); + + boolean firstTime = user.getTermsOfUseVersion() == null; + + // Create the terms of use dialog. + MaterialDialog.Builder builder = createDialogBuilder( + createTermsOfUseMarkup(currentTermsOfUse, firstTime), + // OnPositive callback + () -> { + LOG.info("onClick() the positive button"); + subscriber.onNext(currentTermsOfUse); + }, + // OnNegative callback. + () -> { + LOG.info("onClick() the negative button."); + subscriber.onError(new NotAcceptedTermsOfUseException( + activityContext.getString(R.string + .terms_of_use_cant_continue))); + }); + + // Show the dialog + mainThreadWorker.schedule(() -> termsOfUseDialog = builder.show()); + + + // Add an additional subscription to the subscriber that dismisses the terms of + // use dialog on unsubscribe. + subscriber.add(new Subscription() { + @Override + public void unsubscribe() { + if (termsOfUseDialog != null) + termsOfUseDialog.dismiss(); + } + + @Override + public boolean isUnsubscribed() { + return subscriber.isUnsubscribed(); + } + }); + } + }); + } + + /** + * Creates the dialog for accepting the terms of use. + * + * @param onPositive the action that gets called when the user accepts the terms of use. + * @param onNegative the action that gets called when the user rejects the terms of use. + * @return the created dialog instance. + */ + private MaterialDialog.Builder createDialogBuilder(Spanned content, + Action0 onPositive, Action0 onNegative) { + LOG.info("createDialog()"); + return new MaterialDialog.Builder(activityContext) + .title(R.string.terms_of_use_title) + .content(content) + .positiveText(R.string.terms_of_use_accept) + .negativeText(R.string.terms_of_use_reject) + .cancelable(false) + .onPositive((materialDialog, dialogAction) -> + backgroundWorker.schedule(onPositive)) + .onNegative((materialDialog, dialogAction) -> + backgroundWorker.schedule(onNegative)); + } + + private Spanned createTermsOfUseMarkup(TermsOfUse currentTermsOfUse, boolean firstTime) { + StringBuilder sb = new StringBuilder(); + + sb.append("

"); + if (!firstTime) { + sb.append(activityContext.getString(R.string.terms_of_use_sorry)); + } else { + sb.append(activityContext.getString(R.string.terms_of_use_info)); + } + sb.append(":

"); + sb.append(currentTermsOfUse.getContents().replace("", "
")); + + return Html.fromHtml(sb.toString()); + } +} diff --git a/org.envirocar.app/src/org/envirocar/app/views/Utils.java b/org.envirocar.app/src/org/envirocar/app/views/Utils.java deleted file mode 100644 index a27792e12..000000000 --- a/org.envirocar.app/src/org/envirocar/app/views/Utils.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.views; - -import org.envirocar.core.logging.Logger; - -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.location.LocationManager; -import android.os.Build; - -/** - * Utility functions for the application - * - */ -public class Utils { - - private static final Logger logger = Logger.getLogger(Utils.class); - - - public static int getActionBarId() { - try { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - return Class.forName("com.actionbarsherlock.R$id").getField("abs__action_bar_title").getInt(null); - } else { - // Use reflection to get the actionbar title TextView and set - // the custom font. May break in updates. - return Class.forName("com.android.internal.R$id").getField("action_bar_title").getInt(null); - } - } catch (Exception e) { - logger.warn(e.getMessage(), e); - } - return 0; - } - - /** - * Checks whether the GPS is enabled. - * - * @param context - * the current context - * @return true if gps is enabled. - */ - public static boolean isGPSEnabled(Context context) { - return ((LocationManager) context.getSystemService(android.content.Context.LOCATION_SERVICE) != null) && ((LocationManager) context.getSystemService(android.content.Context.LOCATION_SERVICE)).isProviderEnabled(LocationManager.GPS_PROVIDER); - } - - /** - * Checks whether Bluetooth is enabled - * - * @param context - * the current context - * @return true if bluetooth is enabled - */ - public static boolean isBluetoothEnabled(Context context) { - return BluetoothAdapter.getDefaultAdapter() != null && BluetoothAdapter.getDefaultAdapter().isEnabled(); - } - - -} diff --git a/org.envirocar.app/tests/android/java/org/envirocar/app/test/ObfuscationTest.java b/org.envirocar.app/tests/android/java/org/envirocar/app/test/ObfuscationTest.java index 5a8971f99..f1f5ff1d7 100644 --- a/org.envirocar.app/tests/android/java/org/envirocar/app/test/ObfuscationTest.java +++ b/org.envirocar.app/tests/android/java/org/envirocar/app/test/ObfuscationTest.java @@ -36,6 +36,7 @@ import org.junit.Test; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class ObfuscationTest extends InstrumentationTestCase { @@ -73,6 +74,32 @@ public void testObfuscation() throws JSONException, TrackAlreadyFinishedExceptio } } + @Test + public void testObfuscationException() throws JSONException, TrackAlreadyFinishedException { + start = System.currentTimeMillis(); + end = System.currentTimeMillis() + 1000*60; + + first = new MeasurementImpl(51.0, 7.0); + first.setTime(start); + last = new MeasurementImpl(51.03, 7.03); + last.setTime(end); + + Track t = new TrackImpl(); + t.setCar(new CarImpl("id", "man", "mod", Car.FuelType.DIESEL, 1234, 123)); + + t.setMeasurements(Arrays.asList(new MeasurementImpl[] {first, last})); + + Exception exception = null; + try { + TrackUtils.getObfuscatedTrack(t); + } + catch (NoMeasurementsException e) { + exception = e; + } + + Assert.assertNotNull(exception); + } + private Track createTrack() throws TrackAlreadyFinishedException { Track result = new TrackImpl(); result.setCar(new CarImpl("id", "man", "mod", Car.FuelType.DIESEL, 1234, 123)); diff --git a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/FuelSystemStatusTest.java b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/FuelSystemStatusTest.java index 3b45df4ac..21b5a0fb0 100644 --- a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/FuelSystemStatusTest.java +++ b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/FuelSystemStatusTest.java @@ -19,36 +19,32 @@ package org.envirocar.app.test.commands; import junit.framework.Assert; - -import org.envirocar.obd.commands.FuelSystemStatus; -import org.envirocar.obd.commands.PIDUtil; -import org.envirocar.obd.commands.PIDUtil.PID; import org.junit.Test; public class FuelSystemStatusTest { - @Test - public void testCommandParsing() { - FuelSystemStatus cmd = (FuelSystemStatus) PIDUtil.instantiateCommand(PID.FUEL_SYSTEM_STATUS.toString()); - - cmd.setRawData(createRawDataOpenLoop()); - cmd.parseRawData(); - - Assert.assertTrue("Expected to be in open loop.", !cmd.isInClosedLoop()); - - cmd = (FuelSystemStatus) PIDUtil.instantiateCommand(PID.FUEL_SYSTEM_STATUS.toString()); - - cmd.setRawData(createRawDataClosedLoop(cmd)); - cmd.parseRawData(); - - Assert.assertTrue("Expected to be in closed loop.", cmd.isInClosedLoop()); - } - - private byte[] createRawDataClosedLoop(FuelSystemStatus cmd) { - return "41030100".getBytes(); } - - private byte[] createRawDataOpenLoop() { - return "41030200".getBytes(); - } +// @Test +// public void testCommandParsing() { +// FuelSystemStatus cmd = (FuelSystemStatus) PIDUtil.instantiateCommand(PID.FUEL_SYSTEM_STATUS.toString()); +// +// cmd.setRawData(createRawDataOpenLoop()); +// cmd.parseRawData(); +// +// Assert.assertTrue("Expected to be in open loop.", !cmd.isInClosedLoop()); +// +// cmd = (FuelSystemStatus) PIDUtil.instantiateCommand(PID.FUEL_SYSTEM_STATUS.toString()); +// +// cmd.setRawData(createRawDataClosedLoop(cmd)); +// cmd.parseRawData(); +// +// Assert.assertTrue("Expected to be in closed loop.", cmd.isInClosedLoop()); +// } +// +// private byte[] createRawDataClosedLoop(FuelSystemStatus cmd) { +// return "41030100".getBytes(); } +// +// private byte[] createRawDataOpenLoop() { +// return "41030200".getBytes(); +// } } diff --git a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/FuelTrimBankTest.java b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/FuelTrimBankTest.java index e05dc2d12..dfc2efb6d 100644 --- a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/FuelTrimBankTest.java +++ b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/FuelTrimBankTest.java @@ -20,41 +20,39 @@ import junit.framework.Assert; -import org.envirocar.obd.commands.LongTermTrimBank1; -import org.envirocar.obd.commands.ShortTermTrimBank1; import org.junit.Test; public class FuelTrimBankTest { - @Test - public void testShortTermParsing() { - ShortTermTrimBank1 st = new ShortTermTrimBank1(); - st.setRawData(createShortTermData()); - - st.parseRawData(); - - Number result = st.getNumberResult(); - - Assert.assertTrue("Expected 50.0", result.doubleValue() == 50.0); - } - - private byte[] createShortTermData() { - return "4106C0".getBytes(); - } - - @Test - public void testLongTermParsing() { - LongTermTrimBank1 lt = new LongTermTrimBank1(); - lt.setRawData(createLongTermData()); - - lt.parseRawData(); - - Number result = lt.getNumberResult(); - - Assert.assertTrue("Expected -75.0", result.doubleValue() == -75.0); - } - - private byte[] createLongTermData() { - return "410720".getBytes(); - } +// @Test +// public void testShortTermParsing() { +// ShortTermTrimBank1 st = new ShortTermTrimBank1(); +// st.setRawData(createShortTermData()); +// +// st.parseRawData(); +// +// Number result = st.getNumberResult(); +// +// Assert.assertTrue("Expected 50.0", result.doubleValue() == 50.0); +// } +// +// private byte[] createShortTermData() { +// return "4106C0".getBytes(); +// } +// +// @Test +// public void testLongTermParsing() { +// LongTermTrimBank1 lt = new LongTermTrimBank1(); +// lt.setRawData(createLongTermData()); +// +// lt.parseRawData(); +// +// Number result = lt.getNumberResult(); +// +// Assert.assertTrue("Expected -75.0", result.doubleValue() == -75.0); +// } +// +// private byte[] createLongTermData() { +// return "410720".getBytes(); +// } } diff --git a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/IntakeTemperatureTest.java b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/IntakeTemperatureTest.java index 4ebe61b71..a169144ef 100644 --- a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/IntakeTemperatureTest.java +++ b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/IntakeTemperatureTest.java @@ -20,28 +20,26 @@ import junit.framework.Assert; -import org.envirocar.obd.commands.CommonCommand.CommonCommandState; -import org.envirocar.obd.commands.IntakeTemperature; import org.junit.Test; public class IntakeTemperatureTest { - @Test - public void testParsing() { - byte[] bytes = createBytes(); - IntakeTemperature cmd = new IntakeTemperature(); - cmd.setRawData(bytes); - cmd.parseRawData(); - - Assert.assertTrue(cmd.getCommandState() == CommonCommandState.FINISHED); - - double temp = cmd.getNumberResult().doubleValue(); - Assert.assertTrue(temp == 23.0); - } - - private byte[] createBytes() { - return "410F3F".getBytes(); - } +// @Test +// public void testParsing() { +// byte[] bytes = createBytes(); +// IntakeTemperature cmd = new IntakeTemperature(); +// cmd.setRawData(bytes); +// cmd.parseRawData(); +// +// Assert.assertTrue(cmd.getCommandState() == CommonCommandState.FINISHED); +// +// double temp = cmd.getNumberResult().doubleValue(); +// Assert.assertTrue(temp == 23.0); +// } +// +// private byte[] createBytes() { +// return "410F3F".getBytes(); +// } } diff --git a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/O2LambdaProbeTest.java b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/O2LambdaProbeTest.java index c4037af8e..bf0614ef1 100644 --- a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/O2LambdaProbeTest.java +++ b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/O2LambdaProbeTest.java @@ -21,59 +21,49 @@ import android.os.Environment; import android.util.Base64; -import junit.framework.Assert; - import org.envirocar.core.logging.Logger; -import org.envirocar.obd.commands.O2LambdaProbe; -import org.envirocar.obd.commands.O2LambdaProbeCurrent; -import org.envirocar.obd.commands.O2LambdaProbeVoltage; -import org.envirocar.obd.commands.PIDUtil; -import org.envirocar.obd.commands.PIDUtil.PID; -import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import java.math.BigDecimal; - @RunWith(PowerMockRunner.class) @PrepareForTest({ Environment.class, Logger.class, Base64.class}) public class O2LambdaProbeTest { - @Test - public void testVoltageParsing() { - O2LambdaProbeVoltage cmd = (O2LambdaProbeVoltage) PIDUtil.instantiateCommand(PID.O2_LAMBDA_PROBE_1_VOLTAGE); - - cmd.setRawData(createDataVoltage()); - cmd.parseRawData(); - - BigDecimal er = BigDecimal.valueOf(cmd.getEquivalenceRatio()).setScale(2, BigDecimal.ROUND_HALF_UP); - Assert.assertTrue("Expected equivalence ration of 1.52.", er.doubleValue() == 1.52); - - BigDecimal v = BigDecimal.valueOf(cmd.getVoltage()).setScale(2, BigDecimal.ROUND_HALF_UP); - Assert.assertTrue("Expected voltage of 7.5.", v.doubleValue() == 6.08); - } - - @Test - public void testCurrentParsing() { - O2LambdaProbeCurrent cmd = (O2LambdaProbeCurrent) O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_1_CURRENT); - - cmd.setRawData(createDataCurrent()); - cmd.parseRawData(); - - BigDecimal er = BigDecimal.valueOf(cmd.getEquivalenceRatio()).setScale(2, BigDecimal.ROUND_HALF_UP); - Assert.assertTrue("Expected equivalence ration of 1.52.", er.doubleValue() == 1.52); - - BigDecimal c = BigDecimal.valueOf(cmd.getCurrent()).setScale(2, BigDecimal.ROUND_HALF_UP); - Assert.assertTrue("Expected current of 128.", c.doubleValue() == 2.5); - } - - private byte[] createDataCurrent() { - return "4134C2908280".getBytes(); - } - - private byte[] createDataVoltage() { - return "4124C290C290".getBytes(); - } +// @Test +// public void testVoltageParsing() { +// O2LambdaProbeVoltage cmd = (O2LambdaProbeVoltage) PIDUtil.instantiateCommand(PID.O2_LAMBDA_PROBE_1_VOLTAGE); +// +// cmd.setRawData(createDataVoltage()); +// cmd.parseRawData(); +// +// BigDecimal er = BigDecimal.valueOf(cmd.getEquivalenceRatio()).setScale(2, BigDecimal.ROUND_HALF_UP); +// Assert.assertTrue("Expected equivalence ration of 1.52.", er.doubleValue() == 1.52); +// +// BigDecimal v = BigDecimal.valueOf(cmd.getVoltage()).setScale(2, BigDecimal.ROUND_HALF_UP); +// Assert.assertTrue("Expected voltage of 7.5.", v.doubleValue() == 6.08); +// } +// +// @Test +// public void testCurrentParsing() { +// O2LambdaProbeCurrent cmd = (O2LambdaProbeCurrent) O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_1_CURRENT); +// +// cmd.setRawData(createDataCurrent()); +// cmd.parseRawData(); +// +// BigDecimal er = BigDecimal.valueOf(cmd.getEquivalenceRatio()).setScale(2, BigDecimal.ROUND_HALF_UP); +// Assert.assertTrue("Expected equivalence ration of 1.52.", er.doubleValue() == 1.52); +// +// BigDecimal c = BigDecimal.valueOf(cmd.getCurrent()).setScale(2, BigDecimal.ROUND_HALF_UP); +// Assert.assertTrue("Expected current of 128.", c.doubleValue() == 2.5); +// } +// +// private byte[] createDataCurrent() { +// return "4134C2908280".getBytes(); +// } +// +// private byte[] createDataVoltage() { +// return "4124C290C290".getBytes(); +// } } diff --git a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/PIDSupportedTest.java b/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/PIDSupportedTest.java deleted file mode 100644 index 741d29a76..000000000 --- a/org.envirocar.app/tests/unit/java/org/envirocar/app/test/commands/PIDSupportedTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.app.test.commands; - -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; - -import junit.framework.Assert; - -import org.envirocar.obd.commands.PIDSupported; -import org.envirocar.obd.commands.CommonCommand.CommonCommandState; -import org.envirocar.obd.commands.PIDUtil.PID; -import org.junit.Test; - -public class PIDSupportedTest { - - @Test - public void testPIDSupportedParsing() { - PIDSupported cmd = new PIDSupported(); - - cmd.setRawData(createResponseMockup()); - cmd.parseRawData(); - - Set result = cmd.getSupportedPIDs(); - - assertResult(result); - } - - @Test - public void testPIDSupportedFail() { - PIDSupported cmd = new PIDSupported(); - - cmd.setRawData(createResponseFailMockup()); - cmd.parseRawData(); - - Assert.assertTrue(cmd.getCommandState() == CommonCommandState.EXECUTION_ERROR); - } - - private void assertResult(Set result) { - Set expected = new HashSet(); - expected.add(PID.CALCULATED_ENGINE_LOAD); - expected.add(PID.FUEL_PRESSURE); - expected.add(PID.INTAKE_AIR_TEMP); - expected.add(PID.INTAKE_MAP); - expected.add(PID.MAF); - expected.add(PID.RPM); - expected.add(PID.SPEED); - - Assert.assertTrue(String.format(Locale.US, "Size is different. Expected %d, Received %d.", - expected.size(), - result.size()), - result.size() == expected.size()); - - for (PID string : expected) { - Assert.assertTrue(result.contains(string)); - } - } - - private byte[] createResponseMockup() { - StringBuilder sb = new StringBuilder(); - sb.append("4100"); - sb.append("107B0000"); - return sb.toString().getBytes(); - } - - private byte[] createResponseFailMockup() { - StringBuilder sb = new StringBuilder(); - sb.append("4100"); - sb.append("107B0000"); - sb.append("4100"); - sb.append("107B0000"); - return sb.toString().getBytes(); - } - -} - diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/dao/CarDAO.java b/org.envirocar.core/src/main/java/org/envirocar/core/dao/CarDAO.java index fc0a1649a..23bb76a85 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/dao/CarDAO.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/dao/CarDAO.java @@ -1,25 +1,28 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.core.dao; +import android.security.keystore.UserNotAuthenticatedException; + import org.envirocar.core.entity.Car; +import org.envirocar.core.entity.User; import org.envirocar.core.exception.DataCreationFailureException; import org.envirocar.core.exception.DataRetrievalFailureException; import org.envirocar.core.exception.NotConnectedException; @@ -29,7 +32,7 @@ import rx.Observable; -/** +/**p * TODO JavaDoc * * @author dewall @@ -40,6 +43,11 @@ public interface CarDAO { Observable> getAllCarsObservable(); + List getCarsByUser(User user) throws UserNotAuthenticatedException, + NotConnectedException, DataRetrievalFailureException, UnauthorizedException; + + Observable> getCarsByUserObservable(User user); + String createCar(Car car) throws NotConnectedException, DataCreationFailureException, UnauthorizedException; diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/dao/TrackDAO.java b/org.envirocar.core/src/main/java/org/envirocar/core/dao/TrackDAO.java index c6b9505d1..4d47660dd 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/dao/TrackDAO.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/dao/TrackDAO.java @@ -57,11 +57,10 @@ Integer getUserTrackCount() throws DataRetrievalFailureException, NotConnectedEx Integer getTotalTrackCount() throws DataRetrievalFailureException, NotConnectedException; - String createTrack(Track track) throws DataCreationFailureException, NotConnectedException, + Track createTrack(Track track) throws DataCreationFailureException, NotConnectedException, ResourceConflictException, UnauthorizedException; - void deleteTrack(String remoteID) throws DataUpdateFailureException, NotConnectedException, - UnauthorizedException; + Observable createTrackObservable(Track track); void deleteTrack(Track track) throws DataUpdateFailureException, NotConnectedException, UnauthorizedException; diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/entity/Measurement.java b/org.envirocar.core/src/main/java/org/envirocar/core/entity/Measurement.java index 9be429dc9..789f4a775 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/entity/Measurement.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/entity/Measurement.java @@ -18,6 +18,8 @@ */ package org.envirocar.core.entity; +import org.envirocar.core.R; + import java.util.HashMap; import java.util.Map; @@ -28,149 +30,283 @@ */ public interface Measurement extends BaseEntity { + interface PropertyKeyExtension{ + int getStringResource(); + int getUnitResource(); + } + // All measurement values - enum PropertyKey { + enum PropertyKey implements PropertyKeyExtension{ SPEED { + @Override + public int getStringResource() { + return R.string.property_key_speed; + } + public String toString() { return "Speed"; } }, MAF { + @Override + public int getStringResource() { + return R.string.property_key_maf; + } + public String toString() { return "MAF"; } }, CALCULATED_MAF { + @Override + public int getStringResource() { + return R.string.property_key_calc_maf; + } + public String toString() { return "Calculated MAF"; } }, RPM { + @Override + public int getStringResource() { + return R.string.property_key_rpm; + } + public String toString() { return "Rpm"; } }, INTAKE_TEMPERATURE { + @Override + public int getStringResource() { + return R.string.property_key_intake_temp; + } + public String toString() { return "Intake Temperature"; } }, INTAKE_PRESSURE { + @Override + public int getStringResource() { + return R.string.property_key_intake_pressure; + } + public String toString() { return "Intake Pressure"; } }, CO2 { + @Override + public int getStringResource() { + return R.string.property_key_co2; + } + public String toString() { return "CO2"; } }, CONSUMPTION { + @Override + public int getStringResource() { + return R.string.property_key_consumption; + } + public String toString() { return "Consumption"; } }, THROTTLE_POSITON { + @Override + public int getStringResource() { + return R.string.property_key_throttle_position; + } + @Override public String toString() { return "Throttle Position"; } }, ENGINE_LOAD { + @Override + public int getStringResource() { + return R.string.property_key_engine_load; + } + @Override public String toString() { return "Engine Load"; } }, GPS_ACCURACY { + @Override + public int getStringResource() { + return R.string.property_key_gps_accuracy; + } + @Override public String toString() { return "GPS Accuracy"; } }, GPS_SPEED { + @Override + public int getStringResource() { + return R.string.property_key_gps_speed; + } + @Override public String toString() { return "GPS Speed"; } }, GPS_BEARING { + @Override + public int getStringResource() { + return R.string.property_key_gps_bearing; + } + @Override public String toString() { return "GPS Bearing"; } }, GPS_ALTITUDE { + @Override + public int getStringResource() { + return R.string.property_key_gps_altitude; + } + @Override public String toString() { return "GPS Altitude"; } }, GPS_PDOP { + @Override + public int getStringResource() { + return R.string.property_key_gps_pdop; + } + @Override public String toString() { return "GPS PDOP"; } }, GPS_HDOP { + @Override + public int getStringResource() { + return R.string.property_key_gps_hdop; + } + @Override public String toString() { return "GPS HDOP"; } }, GPS_VDOP { + @Override + public int getStringResource() { + return R.string.property_key_gps_vdop; + } + @Override public String toString() { return "GPS VDOP"; } }, LAMBDA_VOLTAGE { + @Override + public int getStringResource() { + return R.string.property_key_lambda_voltage; + } + @Override public String toString() { return "O2 Lambda Voltage"; } }, LAMBDA_VOLTAGE_ER { + @Override + public int getStringResource() { + return R.string.property_key_lambda_voltage_er; + } + @Override public String toString() { return LAMBDA_VOLTAGE.toString().concat(" ER"); } }, LAMBDA_CURRENT { + @Override + public int getStringResource() { + return R.string.property_key_lambda_current; + } + @Override public String toString() { return "O2 Lambda Current"; } }, LAMBDA_CURRENT_ER { + @Override + public int getStringResource() { + return R.string.property_key_lambda_current_er; + } + @Override public String toString() { return LAMBDA_CURRENT.toString().concat(" ER"); } }, FUEL_SYSTEM_LOOP { + @Override + public int getStringResource() { + return R.string.property_key_fuel_system_loop; + } + @Override public String toString() { return "Fuel System Loop"; } }, FUEL_SYSTEM_STATUS_CODE { + @Override + public int getStringResource() { + return R.string.property_key_fuel_system_status_code; + } + @Override public String toString() { return "Fuel System Status Code"; } }, LONG_TERM_TRIM_1 { + @Override + public int getStringResource() { + return R.string.property_key_long_term_trim_1; + } + @Override public String toString() { return "Long-Term Fuel Trim 1"; } }, SHORT_TERM_TRIM_1 { + @Override + public int getStringResource() { + return R.string.property_key_short_term_trim_1; + } + @Override public String toString() { return "Short-Term Fuel Trim 1"; } + }; + @Override + public int getUnitResource() { + return -1; } } @@ -187,11 +323,11 @@ public String toString() { void setTrackId(Track.TrackId trackId); - double getLatitude(); + Double getLatitude(); void setLatitude(double latitude); - double getLongitude(); + Double getLongitude(); void setLongitude(double longitude); diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/entity/MeasurementImpl.java b/org.envirocar.core/src/main/java/org/envirocar/core/entity/MeasurementImpl.java index 3c3372f83..4dcad3964 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/entity/MeasurementImpl.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/entity/MeasurementImpl.java @@ -28,8 +28,8 @@ */ public class MeasurementImpl implements Measurement { protected Track.TrackId trackId; - protected double latitude; - protected double longitude; + protected Double latitude; + protected Double longitude; protected long time; protected Map propertyMap = new HashMap<>(); @@ -62,7 +62,7 @@ public void setTrackId(Track.TrackId trackId) { } @Override - public double getLatitude() { + public Double getLatitude() { return latitude; } @@ -72,7 +72,7 @@ public void setLatitude(double latitude) { } @Override - public double getLongitude() { + public Double getLongitude() { return longitude; } @@ -98,7 +98,9 @@ public Double getProperty(PropertyKey key) { @Override public void setProperty(PropertyKey key, Double value) { - propertyMap.put(key, value); + if (value != null) { + propertyMap.put(key, value); + } } @Override @@ -133,8 +135,8 @@ public Measurement carbonCopy() { @Override public void reset() { - latitude = 0; - longitude = 0; + latitude = null; + longitude = null; synchronized (this) { propertyMap.clear(); diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/entity/Track.java b/org.envirocar.core/src/main/java/org/envirocar/core/entity/Track.java index 1a29956d9..dcce3d428 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/entity/Track.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/entity/Track.java @@ -126,6 +126,10 @@ enum DownloadState { void setMeasurements(List measurements); + boolean hasProperty(Measurement.PropertyKey propertyKey); + + List getSupportedProperties(); + String getRemoteID(); void setRemoteID(String remoteID); diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/entity/TrackImpl.java b/org.envirocar.core/src/main/java/org/envirocar/core/entity/TrackImpl.java index 0b242da04..4c4224685 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/entity/TrackImpl.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/entity/TrackImpl.java @@ -289,6 +289,27 @@ public void setMeasurements(List measurements) { this.measurements = measurements; } + @Override + public boolean hasProperty(Measurement.PropertyKey propertyKey) { + for(Measurement m : measurements){ + if(m.hasProperty(propertyKey)) { + return true; + } + } + return false; + } + + @Override + public List getSupportedProperties() { + List result = new ArrayList<>(); + for(Measurement.PropertyKey key : Measurement.PropertyKey.values()){ + if(hasProperty(key)){ + result.add(key); + } + } + return result; + } + @Override public int compareTo(Track another) { if (downloadState == DownloadState.REMOTE) { diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/events/TrackFinishedEvent.java b/org.envirocar.core/src/main/java/org/envirocar/core/events/TrackFinishedEvent.java index 1296ac1e3..2f8ec6262 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/events/TrackFinishedEvent.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/events/TrackFinishedEvent.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -22,8 +22,9 @@ import org.envirocar.core.entity.Track; - /** + * TODO JavaDoc + * * @author dewall */ public class TrackFinishedEvent { @@ -35,7 +36,7 @@ public class TrackFinishedEvent { * * @param track the finished track */ - public TrackFinishedEvent(final Track track){ + public TrackFinishedEvent(final Track track) { this.mTrack = track; } diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/events/gps/GpsLocationChangedEvent.java b/org.envirocar.core/src/main/java/org/envirocar/core/events/gps/GpsLocationChangedEvent.java index cb366f093..74fd937ae 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/events/gps/GpsLocationChangedEvent.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/events/gps/GpsLocationChangedEvent.java @@ -42,8 +42,6 @@ public GpsLocationChangedEvent(final Location location){ public String toString() { return MoreObjects.toStringHelper(this) .add("Latitude", mLocation.getLatitude()) - .add("Longitude", mLocation.getLongitude()) - .add("Altitude", mLocation.getAltitude()) - .add("Accuracy", mLocation.getAccuracy()).toString(); + .add("Longitude", mLocation.getLongitude()).toString(); } } diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/exception/TrackWithNoValidCarException.java b/org.envirocar.core/src/main/java/org/envirocar/core/exception/TrackWithNoValidCarException.java new file mode 100644 index 000000000..7e1f80770 --- /dev/null +++ b/org.envirocar.core/src/main/java/org/envirocar/core/exception/TrackWithNoValidCarException.java @@ -0,0 +1,18 @@ +package org.envirocar.core.exception; + +/** + * TODO JavaDoc + * + * @author dewall + */ +public class TrackWithNoValidCarException extends Exception { + + /** + * Constructor. + * + * @param message the error message. + */ + public TrackWithNoValidCarException(String message) { + super(message); + } +} diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorActivity.java b/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorActivity.java index be605cc76..a4102bdf6 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorActivity.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorActivity.java @@ -45,11 +45,11 @@ public abstract class BaseInjectorActivity extends AppCompatActivity implements @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mObjectGraph = ((Injector) getApplicationContext()).getObjectGraph().plus (getInjectionModules().toArray()); + super.onCreate(savedInstanceState); + // Inject all variables in this object. injectObjects(this); } diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorFragment.java b/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorFragment.java index a353c9b3d..acefc0a4c 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorFragment.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorFragment.java @@ -97,7 +97,6 @@ public void onAttach(Activity activity) { Preconditions.checkState(mBus != null, "Bus has to be injected before " + "registering the providers and subscribers."); - } if(!mIsRegistered){ diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorService.java b/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorService.java index ddbf1e181..c5159dccd 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorService.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/injection/BaseInjectorService.java @@ -21,9 +21,12 @@ import android.app.Service; import android.content.Intent; import android.os.IBinder; +import android.support.annotation.Nullable; +import com.google.common.base.Preconditions; import com.squareup.otto.Bus; +import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -31,6 +34,7 @@ import dagger.ObjectGraph; /** + * TODO JavaDoc * * @author dewall */ @@ -42,38 +46,38 @@ public abstract class BaseInjectorService extends Service implements Injector, I @Inject protected Bus bus; + @Nullable @Override - public void onCreate() { - super.onCreate(); - - objectGraph = ((Injector) getApplicationContext()).getObjectGraph().plus - (getInjectionModules().toArray()); + public IBinder onBind(Intent intent) { + return null; } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - return super.onStartCommand(intent, flags, startId); - } + public void onCreate() { + super.onCreate(); - @Override - public IBinder onBind(Intent intent) { - return null; + // extend the object graph of the application scope + objectGraph = ((Injector) getApplicationContext()) + .getObjectGraph().plus(getInjectionModules().toArray()); + + // Inject objects + objectGraph.inject(this); } @Override public List getInjectionModules() { - return null; + return new ArrayList<>(); } @Override public ObjectGraph getObjectGraph() { - return null; + return objectGraph; } @Override public void injectObjects(Object instance) { - + Preconditions.checkNotNull(objectGraph, + "Object graph has to be initialized before inejcting"); + objectGraph.inject(instance); } } diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/logging/Logger.java b/org.envirocar.core/src/main/java/org/envirocar/core/logging/Logger.java index 700e2cc38..de607ead1 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/logging/Logger.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/logging/Logger.java @@ -53,6 +53,7 @@ public class Logger { handlers.add(getLocalFileHandler()); } catch (Exception e) { Log.e(AndroidHandler.DEFAULT_TAG, e.getMessage(), e); + handlers.add(new AndroidHandler()); } // try { // handlers.add(new AndroidHandler()); @@ -108,8 +109,8 @@ protected final void log(int level, String message) { StringBuilder sb = new StringBuilder(); sb.append("["); sb.append(this.name); - sb.append(":"); - sb.append(Thread.currentThread().getStackTrace()[5].getLineNumber()); +// sb.append(":"); +// sb.append(Thread.currentThread().getStackTrace()[5].getLineNumber()); sb.append("] "); sb.append(message); @@ -141,6 +142,10 @@ public void info(String message) { log(INFO, message, null); } + public void info(String messageTmp, String... args){ + info(String.format(messageTmp, args)); + } + public void warn(String message) { log(WARNING, message, null); } @@ -166,7 +171,7 @@ public static String convertExceptionToString(Throwable e) { sb.append(e.getClass().getCanonicalName()); sb.append(": "); sb.append(e.getMessage()); - sb.append("; StackTracke: "); + sb.append("; StackTrace: "); sb.append(Util.NEW_LINE_CHAR); int count = 0; @@ -203,6 +208,12 @@ public static void initialize(String appVersion, boolean debugLogging) { sb.append(appVersion); initLogger.info(sb.toString()); + + initLogger.info("Logging enabled. minimumLogLevel="+minimumLogLevel); + initLogger.log(minimumLogLevel, "Log Levels activated"); } + public boolean isEnabled(int level) { + return level <= minimumLogLevel; + } } diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/AbstractConsumptionAlgorithm.java b/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/ConsumptionAlgorithm.java similarity index 68% rename from org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/AbstractConsumptionAlgorithm.java rename to org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/ConsumptionAlgorithm.java index aef8d6229..5e58d9296 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/AbstractConsumptionAlgorithm.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/ConsumptionAlgorithm.java @@ -29,28 +29,27 @@ * * @author dewall */ -public abstract class AbstractConsumptionAlgorithm { +public interface ConsumptionAlgorithm { + + + /** + * An implementation shall calculate the fuel consumption (l/h). + * + * @param measurement the measurement providing the required parameters + * @return fuel consumption in l/h + * @throws FuelConsumptionException if required parameters were missing + * @throws UnsupportedFuelTypeException + */ + double calculateConsumption(Measurement measurement) throws + FuelConsumptionException, UnsupportedFuelTypeException; /** * An implementation shall calculate the CO2 emission (kg/h) for a fuel consumption value (l/h) * * @param consumption fuel consumption in l/h - * @param type see {@link Car.FuelType} * @return CO2 emission in kg/h * @throws FuelConsumptionException if the fuelType is not supported */ - public static double calculateCO2FromConsumption(double consumption, Car.FuelType type) - throws FuelConsumptionException { - if (type == Car.FuelType.GASOLINE) { - return consumption * 2.35; //kg/h - } else if (type == Car.FuelType.DIESEL) { - return consumption * 2.65; //kg/h - } else throw new FuelConsumptionException("Unsupported FuelType " + type); - } - - public abstract double calculateConsumption(Measurement measurement) throws - FuelConsumptionException, UnsupportedFuelTypeException; - - public abstract double calculateCO2FromConsumption(double consumption) throws + double calculateCO2FromConsumption(double consumption) throws FuelConsumptionException; } diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/DieselConsumptionAlgorithm.java b/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/DieselConsumptionAlgorithm.java new file mode 100644 index 000000000..beb91bbee --- /dev/null +++ b/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/DieselConsumptionAlgorithm.java @@ -0,0 +1,126 @@ +package org.envirocar.core.trackprocessing; + +import com.google.common.base.Preconditions; + +import org.envirocar.core.entity.Measurement; +import org.envirocar.core.exception.FuelConsumptionException; +import org.envirocar.core.exception.UnsupportedFuelTypeException; +import org.envirocar.core.logging.Logger; + +import static org.envirocar.core.entity.Measurement.PropertyKey.*; + +/** + * + * This implements the diesel fuel calculations based on the following parameters:
+ * + *
    + *
  • Lambda Voltage equivalence ratio
  • + *
  • MAF
  • + *
  • a set of pre-calculated co-efficients
  • + *
+ * + * Lambda voltage equivalence ratio might not be available in some situations (in deed it is capped + * for some cars), thus a a regression function (using lambda voltage) is used to derive the + * actual lambda voltage equivalence ratio above the capped values: + * + *
lambda ER = x1 / ( x2 - x3 * lambda_voltage )
+ * + * with actual values: + * + *
lambda ER = 0.23478 / ( 0.218911 - 0.18415 * lambda_voltage )
+ * + */ +public class DieselConsumptionAlgorithm implements ConsumptionAlgorithm { + + private static final Logger LOG = Logger.getLogger(DieselConsumptionAlgorithm.class); + + /** + * regression function co-efficients + */ + private static final double CO_EFFICIENT_X1 = 0.23478; + private static final double CO_EFFICIENT_X2 = 0.218911; + private static final double CO_EFFICIENT_X3 = 0.18415; + + /** + * the minimum required air for diesel engines + */ + private static final double MINIMUM_REQUIRED_AIR = 14.5; + + /** + * density of diesel fuel + */ + private static final double FUEL_DENSITY = 0.832; + + @Override + public double calculateConsumption(Measurement measurement) throws FuelConsumptionException, UnsupportedFuelTypeException { + Preconditions.checkNotNull(measurement); + + if (!measurement.hasProperty(LAMBDA_VOLTAGE_ER) + && !measurement.hasProperty(LAMBDA_VOLTAGE)) { + throw new FuelConsumptionException("No lambda voltage values available"); + } + + /** + * we assume a consumption of zero if the lambda voltage exceeds 1.1 + */ + Double lambdaV = measurement.getProperty(LAMBDA_VOLTAGE); + + if (lambdaV > 1.1) { + //TODO check with TU-BS - seems to happen very often + LOG.info("Lambda Voltage > 1.1; this might be no consumption at all?"); + return 0.0; + } + + double lambdaER = calculateLambdaVoltageER(measurement.getProperty(LAMBDA_VOLTAGE_ER), lambdaV); + + //mass air flow in kilogram + double mafKG = resolveMassAirFlow(measurement) / 1000; + + /** + * calculate mass fuel flow in kg/h + */ + double massFuelFlow = ((mafKG / lambdaER) / MINIMUM_REQUIRED_AIR) * 3600; + + /** + * calculate volumetric fuel flow in l/h + */ + return massFuelFlow * FUEL_DENSITY; + } + + private double resolveMassAirFlow(Measurement measurement) throws FuelConsumptionException { + if (measurement.hasProperty(MAF)) { + return measurement.getProperty(MAF); + } + else if (measurement.hasProperty(CALCULATED_MAF)) { + return measurement.getProperty(CALCULATED_MAF); + } + + throw new FuelConsumptionException("No MAF value available"); + } + + private double calculateLambdaVoltageER(double lambdaER, double lambdaV) throws FuelConsumptionException { + /** + * we will use the provided lambda ER if it is less than 1.97 (= the observed capped max) + */ + if (lambdaER <= 1.97) { + return lambdaER; + } + + /** + * we calculate the lambda ER using the regression function + */ + double denominator = CO_EFFICIENT_X2 - CO_EFFICIENT_X3 * lambdaV; + + //check if we might get into divided by zero + if (denominator == 0.0) { + throw new FuelConsumptionException("Invalid lambda parameters would result in division by zero"); + } + + return CO_EFFICIENT_X1 / denominator; + } + + @Override + public double calculateCO2FromConsumption(double consumption) throws FuelConsumptionException { + return consumption * 2.65; //kg/h + } +} diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/BasicConsumptionAlgorithm.java b/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/GasolineConsumptionAlgorithm.java similarity index 60% rename from org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/BasicConsumptionAlgorithm.java rename to org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/GasolineConsumptionAlgorithm.java index 6e6d301d6..3fb4362c9 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/BasicConsumptionAlgorithm.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/GasolineConsumptionAlgorithm.java @@ -18,7 +18,6 @@ */ package org.envirocar.core.trackprocessing; -import org.envirocar.core.entity.Car; import org.envirocar.core.entity.Measurement; import org.envirocar.core.exception.FuelConsumptionException; import org.envirocar.core.exception.UnsupportedFuelTypeException; @@ -28,25 +27,12 @@ * * @author dewall */ -public class BasicConsumptionAlgorithm extends AbstractConsumptionAlgorithm { +public class GasolineConsumptionAlgorithm implements ConsumptionAlgorithm { - private final Car.FuelType fuelType; - - /** - * Constructor. - * - * @param fuelType the fueltype for which it is required to compute the consumption for. - */ - public BasicConsumptionAlgorithm(Car.FuelType fuelType) { - this.fuelType = fuelType; - } @Override public double calculateConsumption(Measurement measurement) throws FuelConsumptionException, UnsupportedFuelTypeException { - if (fuelType == Car.FuelType.DIESEL) - throw new UnsupportedFuelTypeException(Car.FuelType.DIESEL); - double maf; if (measurement.hasProperty(Measurement.PropertyKey.MAF)) { maf = measurement.getProperty(Measurement.PropertyKey.MAF); @@ -54,31 +40,15 @@ public double calculateConsumption(Measurement measurement) throws FuelConsumpti maf = measurement.getProperty(Measurement.PropertyKey.CALCULATED_MAF); } else throw new FuelConsumptionException("Get no MAF value"); - double airFuelRatio; - double fuelDensity; - - switch (fuelType){ - case GASOLINE: - airFuelRatio = 14.7; - fuelDensity = 745; - break; - case DIESEL: - airFuelRatio = 14.5; - fuelDensity = 832; - break; - default: - throw new UnsupportedFuelTypeException( - "FuelType not supported: " + fuelType.toString()); - } + double airFuelRatio = 14.7; + double fuelDensity = 745; //convert from seconds to hour - double consumption = (maf / airFuelRatio) / fuelDensity * 3600; - - return consumption; + return (maf / airFuelRatio) / fuelDensity * 3600; } @Override public double calculateCO2FromConsumption(double consumption) throws FuelConsumptionException { - return calculateCO2FromConsumption(consumption, fuelType); + return consumption * 2.35; //kg/h } } diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/TrackStatisticsProcessor.java b/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/TrackStatisticsProcessor.java index a298b5499..fba9e8532 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/TrackStatisticsProcessor.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/trackprocessing/TrackStatisticsProcessor.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -25,6 +25,7 @@ import org.envirocar.core.exception.FuelConsumptionException; import org.envirocar.core.exception.UnsupportedFuelTypeException; import org.envirocar.core.logging.Logger; +import org.envirocar.core.utils.CarUtils; import java.util.List; @@ -36,7 +37,7 @@ public class TrackStatisticsProcessor { private static final Logger LOG = Logger.getLogger(TrackStatisticsProcessor.class); - protected AbstractConsumptionAlgorithm consumptionAlgorithm; + protected ConsumptionAlgorithm consumptionAlgorithm; /** * Constructor. @@ -44,7 +45,7 @@ public class TrackStatisticsProcessor { * @param fuelType the fuel type of the corresponding car. */ public TrackStatisticsProcessor(Car.FuelType fuelType) { - consumptionAlgorithm = new BasicConsumptionAlgorithm(fuelType); + this.consumptionAlgorithm = CarUtils.resolveConsumptionAlgorithm(fuelType); } public double computeDistanceOfTrack(List measurements) { @@ -72,8 +73,11 @@ public double computeDistanceOfTrack(List measurements) { return distance / 1000.0d; } - public double getCO2Average(List measurements) throws FuelConsumptionException { + public Double getCO2Average(List measurements) throws FuelConsumptionException { double co2Avg = 0.0; + if (consumptionAlgorithm == null) { + return null; + } for (Measurement measurement : measurements) { Double property = measurement.getProperty(Measurement.PropertyKey.CONSUMPTION); @@ -81,15 +85,19 @@ public double getCO2Average(List measurements) throws FuelConsumpti if (property != null) { co2Avg += consumptionAlgorithm.calculateCO2FromConsumption(property); } + } co2Avg /= measurements.size(); return co2Avg; } - public double getFuelConsumptionPerHour(List measurements) throws + public Double getFuelConsumptionPerHour(List measurements) throws FuelConsumptionException { double consumption = 0.0; + if (consumptionAlgorithm == null) { + return null; + } int consideredCount = 0; for (Measurement measurement : measurements) { diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/util/TrackMetadata.java b/org.envirocar.core/src/main/java/org/envirocar/core/util/TrackMetadata.java index 3df737c24..806bb7c41 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/util/TrackMetadata.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/util/TrackMetadata.java @@ -18,8 +18,6 @@ */ package org.envirocar.core.util; -import android.content.Context; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -50,7 +48,7 @@ public static TrackMetadata fromJson(String string) throws JSONException { JSONArray names = json.names(); for (int i = 0; i < names.length(); i++) { String key = names.get(i).toString(); - result.putEntry(key, json.getString(key)); + result.add(key, json.getString(key)); } return result; @@ -61,19 +59,21 @@ public TrackMetadata() { // public TrackMetadata(Context context) { // UserManager userManager = ((Injector) context).getObjectGraph().get(UserManager.class); -// putEntry(APP_VERSION, Util.getVersionString(context)); -// putEntry(TOU_VERSION, userManager.getUser().getTouVersion()); +// add(APP_VERSION, Util.getVersionString(context)); +// add(TOU_VERSION, userManager.getUser().getTouVersion()); // } public TrackMetadata(String appVersion, String touVersion){ - putEntry(APP_VERSION, appVersion); - putEntry(TOU_VERSION, touVersion); + add(APP_VERSION, appVersion); + add(TOU_VERSION, touVersion); } - public void putEntry(String key, String value) { - if (value == null) return; - - this.entries.put(key, value); + public TrackMetadata add(String key, String value) { + if (value != null) { + this.entries.put(key, value); + } + + return this; } public void merge(TrackMetadata newMetadata) { diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/utils/BroadcastUtils.java b/org.envirocar.core/src/main/java/org/envirocar/core/utils/BroadcastUtils.java index 72bc22645..692c8a95e 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/utils/BroadcastUtils.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/utils/BroadcastUtils.java @@ -41,6 +41,9 @@ public static final Observable createBroadcastObservable( return Observable.create(new Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { + // Start it + subscriber.onStart(); + // Broadcast receiver for the specific intentfilter final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/utils/CarUtils.java b/org.envirocar.core/src/main/java/org/envirocar/core/utils/CarUtils.java index 3d10efaf1..6f5508436 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/utils/CarUtils.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/utils/CarUtils.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -24,6 +24,9 @@ import org.envirocar.core.entity.Car; import org.envirocar.core.logging.Logger; +import org.envirocar.core.trackprocessing.ConsumptionAlgorithm; +import org.envirocar.core.trackprocessing.DieselConsumptionAlgorithm; +import org.envirocar.core.trackprocessing.GasolineConsumptionAlgorithm; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -43,7 +46,8 @@ public static Car instantiateCar(String object) { ObjectInputStream ois = null; try { - Base64InputStream b64 = new Base64InputStream(new ByteArrayInputStream(object.getBytes()), Base64.DEFAULT); + Base64InputStream b64 = new Base64InputStream(new ByteArrayInputStream(object + .getBytes()), Base64.DEFAULT); ois = new ObjectInputStream(b64); Object carObject = ois.readObject(); if (carObject instanceof Car) { @@ -56,7 +60,7 @@ public static Car instantiateCar(String object) { } catch (ClassNotFoundException e) { logger.warn(e.getMessage(), e); return null; - } catch (ClassCastException e){ + } catch (ClassCastException e) { logger.warn(e.getMessage(), e); return null; } finally { @@ -102,4 +106,38 @@ public static String serializeCar(Car car) { } return null; } + + public static ConsumptionAlgorithm resolveConsumptionAlgorithm(Car.FuelType fuelType) { + if (fuelType == Car.FuelType.DIESEL) { + return new DieselConsumptionAlgorithm(); + } else { + return new GasolineConsumptionAlgorithm(); + } + } + + public static String carToStringWithLinebreak(Car car){ + StringBuilder sb = new StringBuilder(); + sb.append(car.getManufacturer()); + sb.append(" - "); + sb.append(car.getModel()); + sb.append("\n"); + sb.append(car.getConstructionYear()); + sb.append(", "); + sb.append(car.getFuelType()); + sb.append(", "); + sb.append(car.getEngineDisplacement()); + sb.append("cc"); + return sb.toString(); + } + + /** + * Returns true if the current remote id of the car starts with the temporary prefix + * + * @param car the car to check + * @return true if the car has been uploaded. + */ + public static boolean isCarUploaded(Car car) { + return !car.getId().startsWith(Car.TEMPORARY_SENSOR_ID); + } + } diff --git a/org.envirocar.core/src/main/java/org/envirocar/core/utils/TrackUtils.java b/org.envirocar.core/src/main/java/org/envirocar/core/utils/TrackUtils.java index f3ad4d10e..fd44278fd 100644 --- a/org.envirocar.core/src/main/java/org/envirocar/core/utils/TrackUtils.java +++ b/org.envirocar.core/src/main/java/org/envirocar/core/utils/TrackUtils.java @@ -58,19 +58,24 @@ private static List getNonObfuscatedMeasurements(Track track) throw List measurements = track.getMeasurements(); List nonPrivateMeasurements = new ArrayList(); - int first = determineFirstNonObfuscatedIndex(measurements, track); - int last = determineLastNonObfuscatedIndex(measurements, track); + try { + int first = determineFirstNonObfuscatedIndex(measurements, track); + int last = determineLastNonObfuscatedIndex(measurements, track); - if (first == -1 || last == -1) { - LOG.warn("Could not determine first/last non-obfuscated measurements. Returning originals"); - return measurements; - } + if (first == -1 || last == -1) { + LOG.warn("Could not determine first/last non-obfuscated measurements."); + throw new NoMeasurementsException("No obfuscated measurements available."); + } - for (int i = first; i <= last; i++) { - nonPrivateMeasurements.add(measurements.get(i)); - } + for (int i = first; i <= last; i++) { + nonPrivateMeasurements.add(measurements.get(i)); + } - return nonPrivateMeasurements; + return nonPrivateMeasurements; + } catch (NoMeasurementsException e) { + LOG.warn("Could not obfuscate track", e); + throw e; + } } private static int determineFirstNonObfuscatedIndex(List measurements, Track track) throws NoMeasurementsException { diff --git a/org.envirocar.core/src/main/res/values-de/strings_property_keys.xml b/org.envirocar.core/src/main/res/values-de/strings_property_keys.xml new file mode 100644 index 000000000..5b6e3bb2e --- /dev/null +++ b/org.envirocar.core/src/main/res/values-de/strings_property_keys.xml @@ -0,0 +1,30 @@ + + + + Geschwindigkeit + Luftmassenstrom + Berechn. Luftmassenstrom + RPM + Eintrittstemperatur + Einlassdruck + CO2 + Geschätzter Verbrauch + Drosselklappenstellung + Motorlast + GPS Genauigkeit + GPS Geschwindigkeit + GPS Bearing + GPS Altitude + GPS PDOP + GPS HDOP + GPS VDOP + O2 Lambda Spannung + O2 Lambda Spannung ER + O2 Lambda Current + O2 Lambda Current ER + Fuel System Loop + Fuel System Status Code + Long-Term Fuel Trim 1 + Short-Term Fuel Trim 1 + + diff --git a/org.envirocar.core/src/main/res/values/strings_property_keys.xml b/org.envirocar.core/src/main/res/values/strings_property_keys.xml new file mode 100644 index 000000000..4c96dd8d5 --- /dev/null +++ b/org.envirocar.core/src/main/res/values/strings_property_keys.xml @@ -0,0 +1,30 @@ + + + + Speed + MAF + Calculated MAF + RPM + Intake Temperature + Intake Pressure + CO2 + Consumption + Throttle Position + Engine Load + GPS Accuracy + GPS Speed + GPS Bearing + GPS Altitude + GPS PDOP + GPS HDOP + GPS VDOP + O2 Lambda Voltage + O2 Lambda Voltage ER + O2 Lambda Current + O2 Lambda Current ER + Fuel System Loop + Fuel System Status Code + Long-Term Fuel Trim 1 + Short-Term Fuel Trim 1 + + diff --git a/org.envirocar.obd/build.gradle b/org.envirocar.obd/build.gradle index 63b763d26..ffb9f9b16 100644 --- a/org.envirocar.obd/build.gradle +++ b/org.envirocar.obd/build.gradle @@ -28,6 +28,7 @@ android { dependencies { testCompile 'junit:junit:4.12' + androidTestCompile 'junit:junit:4.12' compile supportV7 compile rootProject.ext.dagger @@ -37,5 +38,6 @@ dependencies { compile rootProject.ext.rxJava compile project(':org.envirocar.core') + compile 'junit:junit:4.12' } diff --git a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/ApplicationTest.java b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/ApplicationTest.java deleted file mode 100644 index f2944b549..000000000 --- a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/ApplicationTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/SyncAdapterTest.java b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/SyncAdapterTest.java new file mode 100644 index 000000000..cd247c374 --- /dev/null +++ b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/SyncAdapterTest.java @@ -0,0 +1,143 @@ +package org.envirocar.obd.adapter; + +import android.test.InstrumentationTestCase; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.PIDUtil; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.commands.request.PIDCommand; +import org.envirocar.obd.commands.request.elm.ConfigurationCommand; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.commands.response.entity.MAFResponse; +import org.envirocar.obd.commands.response.entity.SpeedResponse; +import org.envirocar.obd.exception.AdapterFailedException; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; + +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +public class SyncAdapterTest extends InstrumentationTestCase { + + @Test + public void testInit() throws InterruptedException { + MockAdapter adapter = new MockAdapter(); + + ByteArrayInputStream bis = new ByteArrayInputStream("OK>OK>4100BE1FA813>1A090F01>".getBytes()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + TestSubscriber testSubscriber = new TestSubscriber<>(); + + Subscription sub = adapter.initialize(bis, bos) + .observeOn(Schedulers.immediate()) + .subscribeOn(Schedulers.immediate()) + .subscribe(testSubscriber); + + testSubscriber.assertNoErrors(); + testSubscriber.assertValueCount(1); + } + + @Test + public void testData() { + MockAdapter adapter = new MockAdapter(); + + //Meta, Meta, PIDSupported0x00, PIDSupported0x20, data, data + ByteArrayInputStream bis = new ByteArrayInputStream("OK>OK>4100BE1FA813>41201A090F01>4110aabb>410daabb>".getBytes()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + TestSubscriber initSubscriber = new TestSubscriber<>(); + + adapter.initialize(bis, bos) + .subscribeOn(Schedulers.immediate()) + .observeOn(Schedulers.immediate()) + .subscribe(initSubscriber); + + initSubscriber.assertNoErrors(); + initSubscriber.assertValueCount(1); + + /** + * now the actual data stuff + */ + TestSubscriber testSubscriber = new TestSubscriber<>(); + + adapter.observe() + .observeOn(Schedulers.immediate()) + .subscribeOn(Schedulers.immediate()) + .subscribe(testSubscriber); + + testSubscriber.assertNoErrors(); + + List received = testSubscriber.getOnNextEvents(); + + Assert.assertThat(received.size(), CoreMatchers.is(2)); + + Assert.assertThat(received.get(0), CoreMatchers.is(CoreMatchers.instanceOf(MAFResponse.class))); + Assert.assertThat(received.get(1), CoreMatchers.is(CoreMatchers.instanceOf(SpeedResponse.class))); + } + + private static class MockAdapter extends SyncAdapter { + + private final Queue initCommands; + private final Queue dataCommands; + private int metaResponse; + + public MockAdapter() { + this.initCommands = new ArrayDeque<>(); + + this.initCommands.offer(ConfigurationCommand.instance(ConfigurationCommand.Instance.ECHO_OFF)); + this.initCommands.offer(ConfigurationCommand.instance(ConfigurationCommand.Instance.HEADERS_OFF)); + + this.dataCommands = new ArrayDeque<>(); + this.dataCommands.offer(PIDUtil.instantiateCommand(PID.MAF)); + this.dataCommands.offer(PIDUtil.instantiateCommand(PID.SPEED)); + } + + public int getMetaResponse() { + return metaResponse; + } + + @Override + protected BasicCommand pollNextInitializationCommand() { + return this.initCommands.poll(); + } + + @Override + protected List providePendingCommands() { + return new ArrayList<>(this.dataCommands); + } + + @Override + protected PIDCommand pollNextCommand() throws AdapterFailedException { + return this.dataCommands.poll(); + } + + @Override + protected boolean analyzeMetadataResponse(byte[] response, BasicCommand sentCommand) throws AdapterFailedException { + return ++metaResponse >= 2; + } + + @Override + protected byte[] preProcess(byte[] bytes) { + return bytes; + } + + @Override + public boolean supportsDevice(String deviceName) { + return true; + } + + @Override + public boolean hasCertifiedConnection() { + return true; + } + } +} diff --git a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/AsyncAdapterTest.java b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/AsyncAdapterTest.java new file mode 100644 index 000000000..bbb974572 --- /dev/null +++ b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/AsyncAdapterTest.java @@ -0,0 +1,111 @@ +package org.envirocar.obd.adapter.async; + +import android.test.InstrumentationTestCase; + +import org.envirocar.obd.adapter.ResponseQuirkWorkaround; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.commands.response.entity.SpeedResponse; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.UnmatchedResponseException; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.List; +import java.util.Queue; + +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +public class AsyncAdapterTest extends InstrumentationTestCase { + + @Test + public void testWorkflow() { + MockAdapter adapter = new MockAdapter(); + + InputStream is = new ByteArrayInputStream("DUMMY>DUMMY>".getBytes()); + OutputStream os = new ByteArrayOutputStream(); + + TestSubscriber initSub = new TestSubscriber<>(); + + adapter.initialize(is, os).subscribeOn(Schedulers.immediate()) + .observeOn(Schedulers.immediate()) + .subscribe(initSub); + + initSub.assertNoErrors(); + initSub.assertValueCount(1); + + TestSubscriber dataSub = new TestSubscriber<>(); + + adapter.observe().subscribeOn(Schedulers.immediate()) + .observeOn(Schedulers.immediate()) + .subscribe(dataSub); + + List list = dataSub.getOnNextEvents(); + dataSub.assertNoErrors(); + Assert.assertThat(list.size(), CoreMatchers.is(1)); + Assert.assertThat(list.get(0), CoreMatchers.is(CoreMatchers.instanceOf(SpeedResponse.class))); + } + + private static class MockAdapter extends AsyncAdapter { + + private static final char CARRIAGE_RETURN = '\r'; + private static final char END_OF_LINE_RESPONSE = '>'; + private final Queue commands; + + public MockAdapter() { + super(CARRIAGE_RETURN, END_OF_LINE_RESPONSE); + this.commands = new ArrayDeque<>(); + + this.commands.offer(new CarriageReturnCommand()); + this.commands.offer(new CycleCommand(Collections.singletonList(CycleCommand.DriveDeckPID.SPEED))); + } + + @Override + protected boolean hasEstablishedConnection() { + return true; + } + + @Override + protected ResponseQuirkWorkaround getQuirk() { + return null; + } + + @Override + protected BasicCommand pollNextCommand() { + return this.commands.poll(); + } + + @Override + protected DataResponse processResponse(byte[] bytes) throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + if (new String(bytes).equals("DUMMY")) { + return new SpeedResponse(123); + } + return null; + } + + @Override + public boolean supportsDevice(String deviceName) { + return true; + } + + @Override + public boolean hasCertifiedConnection() { + return true; + } + + @Override + public long getExpectedInitPeriod() { + return 15000; + } + } +} diff --git a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/CyclicCommandTest.java b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/CyclicCommandTest.java new file mode 100644 index 000000000..72490636e --- /dev/null +++ b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/CyclicCommandTest.java @@ -0,0 +1,30 @@ +package org.envirocar.obd.adapter.async; + +import android.test.InstrumentationTestCase; + +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class CyclicCommandTest extends InstrumentationTestCase { + + @Test + public void testBytes() { + List pidList = new ArrayList<>(); + + pidList.add(CycleCommand.DriveDeckPID.SPEED); + pidList.add(CycleCommand.DriveDeckPID.RPM); + pidList.add(CycleCommand.DriveDeckPID.IAP); + pidList.add(CycleCommand.DriveDeckPID.IAT); + + CycleCommand cycleCommand = new CycleCommand(pidList); + + byte[] expected = new byte[] {97, 49, 55, 26, 25, 24, 28}; + + Assert.assertThat(expected, CoreMatchers.equalTo(cycleCommand.getOutputBytes())); + } + +} diff --git a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/DriveDeckParserTest.java b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/DriveDeckParserTest.java new file mode 100644 index 000000000..2a21e4cb3 --- /dev/null +++ b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/DriveDeckParserTest.java @@ -0,0 +1,66 @@ +package org.envirocar.obd.adapter.async; + +import android.test.InstrumentationTestCase; +import android.util.Base64; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.commands.response.entity.EngineRPMResponse; +import org.envirocar.obd.commands.response.entity.LambdaProbeVoltageResponse; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.UnmatchedResponseException; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Test; + +public class DriveDeckParserTest extends InstrumentationTestCase { + + @Test + public void testSpeedParsing() throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + byte[] bytes = new byte[]{66, 52, 49, 60, (byte) 77, (byte) 0, 60, 0, 0, 32, 32}; + + DriveDeckSportAdapter dd = new DriveDeckSportAdapter(); + + DataResponse resp = dd.processResponse(bytes); + + Assert.assertThat(resp.getPid(), CoreMatchers.is(PID.SPEED)); + Assert.assertThat(resp.getValue(), CoreMatchers.is(77)); + } + + @Test + public void testLambdaParsing() throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + byte[] decode = Base64.decode("QjREPH+dPAAAPFzy", Base64.DEFAULT); + + DriveDeckSportAdapter dd = new DriveDeckSportAdapter(); + + DataResponse resp = dd.processResponse(decode); + + Assert.assertThat(resp, CoreMatchers.instanceOf(LambdaProbeVoltageResponse.class)); + } + + @Test + public void testPIDSupportedParsing() throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + byte[] decode = Base64.decode("QjcwN0U4MDA8mDs8oBM=", Base64.DEFAULT); + + DriveDeckSportAdapter driveDeckSportAdapter = new DriveDeckSportAdapter(); + + driveDeckSportAdapter.processResponse(decode); + + Assert.assertThat(driveDeckSportAdapter.getSupportedPIDs().size(), CoreMatchers.not(0)); + } + + @Test + public void testRPMSpecialCaseParsing() throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + byte[] decode = Base64.decode("QjUxPAAQPAwYPAAAPF0u", Base64.DEFAULT); + + DriveDeckSportAdapter dd = new DriveDeckSportAdapter(); + + DataResponse resp = dd.processResponse(decode); + + Assert.assertThat(resp, CoreMatchers.instanceOf(EngineRPMResponse.class)); + Assert.assertThat(resp.getValue(), CoreMatchers.is(774)); + } + +} diff --git a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/PIDSupportedQuirkTest.java b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/PIDSupportedQuirkTest.java new file mode 100644 index 000000000..43e51dca1 --- /dev/null +++ b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/adapter/async/PIDSupportedQuirkTest.java @@ -0,0 +1,47 @@ +package org.envirocar.obd.adapter.async; + +import android.test.InstrumentationTestCase; +import android.util.Base64; + +import org.envirocar.obd.adapter.CommandExecutor; +import org.envirocar.obd.exception.StreamFinishedException; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + +public class PIDSupportedQuirkTest extends InstrumentationTestCase { + + @Test + public void testQuirk() { + byte[] bytesWait = Base64.decode("QjcwN0U4MDA8vg==", Base64.DEFAULT); + byte[] bytesFull = Base64.decode("QjcwN0U4MDA8vj48uBM=", Base64.DEFAULT); + + PIDSupportedQuirk quirk = new PIDSupportedQuirk(); + Assert.assertTrue(quirk.shouldWaitForNextTokenLine(bytesWait)); + Assert.assertFalse(quirk.shouldWaitForNextTokenLine(bytesFull)); + } + + @Test + public void testQuirkIntegration() throws IOException, StreamFinishedException { + byte[] bytesFull = Base64.decode("QjcwN0U4MDA8vj48uBM=", Base64.DEFAULT); + byte[] bytesFullWithEnd = Arrays.copyOf(bytesFull, bytesFull.length+1); + bytesFullWithEnd[bytesFullWithEnd.length-1] = DriveDeckSportAdapter.END_OF_LINE_RESPONSE; + + PIDSupportedQuirk quirk = new PIDSupportedQuirk(); + + CommandExecutor executor = new CommandExecutor(new ByteArrayInputStream(bytesFullWithEnd), new ByteArrayOutputStream(), + Collections.emptySet(), + DriveDeckSportAdapter.END_OF_LINE_RESPONSE, DriveDeckSportAdapter.CARRIAGE_RETURN); + executor.setQuirk(quirk); + + byte[] bytes = executor.retrieveLatestResponse(); + + Assert.assertTrue(Arrays.equals(bytes, bytesFull)); + } + +} diff --git a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/commands/response/PIDSupportedTest.java b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/commands/response/PIDSupportedTest.java new file mode 100644 index 000000000..49677199d --- /dev/null +++ b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/commands/response/PIDSupportedTest.java @@ -0,0 +1,74 @@ +package org.envirocar.obd.commands.response; + +import android.test.InstrumentationTestCase; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.PIDSupported; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.UnmatchedResponseException; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Set; + +public class PIDSupportedTest extends InstrumentationTestCase { + + @Test + public void testParsing() throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + PIDSupported pidSupported = new PIDSupported(); + + Set pids = pidSupported.parsePIDs("4100BE1FA813".getBytes()); + + Assert.assertThat(pids, CoreMatchers.hasItems( + PID.FUEL_SYSTEM_STATUS, + PID.CALCULATED_ENGINE_LOAD, + PID.SHORT_TERM_FUEL_TRIM_BANK_1, + PID.LONG_TERM_FUEL_TRIM_BANK_1, + PID.RPM, + PID.SPEED, + PID.INTAKE_AIR_TEMP, + PID.MAF, + PID.TPS + )); + Assert.assertThat(pids, CoreMatchers.not(CoreMatchers.hasItems( + PID.INTAKE_MAP + ))); + + pidSupported = new PIDSupported("20"); + + pids = pidSupported.parsePIDs("41201A090F01".getBytes()); + + Assert.assertThat(pids, CoreMatchers.hasItems( + PID.O2_LAMBDA_PROBE_1_VOLTAGE, + PID.O2_LAMBDA_PROBE_2_VOLTAGE, + PID.O2_LAMBDA_PROBE_4_VOLTAGE, + PID.O2_LAMBDA_PROBE_2_CURRENT, + PID.O2_LAMBDA_PROBE_3_CURRENT, + PID.O2_LAMBDA_PROBE_4_CURRENT, + PID.O2_LAMBDA_PROBE_5_CURRENT + )); + Assert.assertThat(pids, CoreMatchers.not(CoreMatchers.hasItems( + PID.O2_LAMBDA_PROBE_3_VOLTAGE, + PID.O2_LAMBDA_PROBE_5_VOLTAGE, + PID.O2_LAMBDA_PROBE_6_VOLTAGE, + PID.O2_LAMBDA_PROBE_7_VOLTAGE, + PID.O2_LAMBDA_PROBE_8_VOLTAGE, + PID.O2_LAMBDA_PROBE_1_CURRENT, + PID.O2_LAMBDA_PROBE_6_CURRENT, + PID.O2_LAMBDA_PROBE_7_CURRENT, + PID.O2_LAMBDA_PROBE_8_CURRENT + ))); + } + + @Test + public void testMalformedResponses() throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + PIDSupported pidSupported = new PIDSupported(); + Set pids = pidSupported.parsePIDs("SEARCHING...4100BE3EB813".getBytes()); + + Assert.assertThat(pids, CoreMatchers.hasItems(PID.SPEED)); + } + +} diff --git a/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/commands/response/ResponseParserTest.java b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/commands/response/ResponseParserTest.java new file mode 100644 index 000000000..7bdb0ed13 --- /dev/null +++ b/org.envirocar.obd/src/androidTest/java/org/envirocar/obd/commands/response/ResponseParserTest.java @@ -0,0 +1,46 @@ +package org.envirocar.obd.commands.response; + +import android.test.InstrumentationTestCase; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.entity.LambdaProbeVoltageResponse; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.UnmatchedResponseException; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Test; + +public class ResponseParserTest extends InstrumentationTestCase { + + @Test + public void testLambdaParsing() throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + ResponseParser responseParser = new ResponseParser(); + + DataResponse response = responseParser.parse("412407FF0028".getBytes()); + + Assert.assertThat(response.getPid(), CoreMatchers.is(PID.O2_LAMBDA_PROBE_1_VOLTAGE)); + Assert.assertThat(response.isComposite(), CoreMatchers.is(true)); + + Number[] composites = response.getCompositeValues(); + + Assert.assertThat(composites.length, CoreMatchers.is(2)); + + //ER: byte A = 7, byte B = 255 --> ((7*256)+255)*2/65535 + Assert.assertThat(composites[0], CoreMatchers.is(((7*256)+255)/32768d)); + //Voltage: byte C = 0, byte D = 40--> ((0*256)+40)*8/65535 + Assert.assertThat(composites[1], CoreMatchers.is(((0*256)+40)/8192d)); + } + + public void testLambdaSwitched() throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + ResponseParser responseParser = new ResponseParser(); + + DataResponse parse = responseParser.parse("41241DBC3B48".getBytes()); + + DataResponse parseSwitch = responseParser.parse("41243B481DBC".getBytes()); + + Assert.assertThat(parse, CoreMatchers.instanceOf(LambdaProbeVoltageResponse.class)); + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/Collector.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/Collector.java deleted file mode 100644 index e015b9099..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/Collector.java +++ /dev/null @@ -1,350 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd; - -import android.content.Context; -import android.location.Location; - -import com.squareup.otto.Bus; -import com.squareup.otto.Subscribe; - -import org.envirocar.core.entity.Car; -import org.envirocar.core.entity.Measurement; -import org.envirocar.core.entity.MeasurementImpl; -import org.envirocar.core.events.NewMeasurementEvent; -import org.envirocar.obd.events.BluetoothServiceStateChangedEvent; -import org.envirocar.core.events.gps.GpsDOP; -import org.envirocar.core.events.gps.GpsLocationChangedEvent; -import org.envirocar.core.exception.FuelConsumptionException; -import org.envirocar.core.exception.NoMeasurementsException; -import org.envirocar.core.exception.UnsupportedFuelTypeException; -import org.envirocar.core.injection.Injector; -import org.envirocar.core.logging.Logger; -import org.envirocar.core.trackprocessing.AbstractCalculatedMAFAlgorithm; -import org.envirocar.core.trackprocessing.AbstractConsumptionAlgorithm; -import org.envirocar.core.trackprocessing.BasicConsumptionAlgorithm; -import org.envirocar.core.trackprocessing.CalculatedMAFWithStaticVolumetricEfficiency; -import org.envirocar.obd.commands.O2LambdaProbe; -import org.envirocar.obd.commands.O2LambdaProbeCurrent; -import org.envirocar.obd.commands.O2LambdaProbeVoltage; -import org.envirocar.obd.events.Co2Event; -import org.envirocar.obd.events.ConsumptionEvent; -import org.envirocar.obd.service.BluetoothServiceState; - -import javax.inject.Inject; - -public class Collector { - private static final Logger LOG = Logger.getLogger(Collector.class); - - public static final int DEFAULT_SAMPLING_RATE_DELTA = 5000; - private Measurement measurement = new MeasurementImpl(); - private MeasurementListener callback; - private Car car; - private AbstractCalculatedMAFAlgorithm mafAlgorithm; - private AbstractConsumptionAlgorithm consumptionAlgorithm; - private boolean fuelTypeNotSupportedLogged; - private long samplingRateDelta = 5000; - - @Inject - protected Bus mBus; - - private boolean mIsRegisteredOnTheBus; - - /** - * Constructor. - * - * @param context the context of the current scope. - * @param l - * @param car - */ - public Collector(Context context, MeasurementListener l, Car car) { - this(context, l, car, DEFAULT_SAMPLING_RATE_DELTA); - } - - /** - * Constructor. - * - * @param context the context of the current scope. - * @param l - * @param car - * @param samplingDelta - */ - public Collector(Context context, MeasurementListener l, Car car, int samplingDelta) { - // First, inject all annotated fields. - ((Injector) context).injectObjects(this); - - // then register on the bus. - this.mBus.register(this); - - this.callback = l; - this.car = car; - - this.samplingRateDelta = samplingDelta; - - this.mafAlgorithm = new CalculatedMAFWithStaticVolumetricEfficiency(this.car); - LOG.info("Using MAF Algorithm " + this.mafAlgorithm.getClass()); - this.consumptionAlgorithm = new BasicConsumptionAlgorithm(this.car.getFuelType()); - LOG.info("Using Consumption Algorithm " + this.consumptionAlgorithm.getClass()); - - resetMeasurement(); - } - - - private void resetMeasurement() { - measurement.reset(); - } - - public void newLocation(Location l) { - this.measurement.setLatitude(l.getLatitude()); - this.measurement.setLongitude(l.getLongitude()); - - if (l.hasAccuracy() && l.getAccuracy() != 0.0f) { - this.measurement.setProperty(Measurement.PropertyKey.GPS_ACCURACY, (double) l - .getAccuracy()); - } - if (l.hasBearing()) { - this.measurement.setProperty(Measurement.PropertyKey.GPS_BEARING, (double) l - .getBearing()); - } - if (l.hasAltitude()) { - this.measurement.setProperty(Measurement.PropertyKey.GPS_ALTITUDE, l.getAltitude()); - } - if (l.hasSpeed()) { - this.measurement.setProperty(Measurement.PropertyKey.GPS_SPEED, - meterPerSecondToKilometerPerHour( - (double) l.getSpeed())); - } - - checkStateAndPush(); - } - - private Double meterPerSecondToKilometerPerHour(double speed) { - return speed * (36.0 / 10.0); - } - - public void newSpeed(int s) { - this.measurement.setProperty(Measurement.PropertyKey.SPEED, Double.valueOf(s)); - // checkStateAndPush(); - } - - public void newMAF(double m) { - this.measurement.setProperty(Measurement.PropertyKey.MAF, m); - // checkStateAndPush(); - fireConsumptionEvent(); - } - - public void newRPM(int r) { - this.measurement.setProperty(Measurement.PropertyKey.RPM, Double.valueOf(r)); - checkAndCreateCalculatedMAF(); - // checkStateAndPush(); - } - - /** - * method checks if the current measurement has everything available for - * calculating the MAF, and then calculates it. - */ - private void checkAndCreateCalculatedMAF() { - if (this.measurement.getProperty(Measurement.PropertyKey.RPM) != null && - this.measurement.getProperty(Measurement.PropertyKey.INTAKE_PRESSURE) != null && - this.measurement.getProperty(Measurement.PropertyKey.INTAKE_TEMPERATURE) != null) { - try { - this.measurement.setProperty(Measurement.PropertyKey.CALCULATED_MAF, this - .mafAlgorithm - .calculateMAF(this.measurement)); - fireConsumptionEvent(); - } catch (NoMeasurementsException e) { - LOG.warn(e.getMessage(), e); - } - } - } - - private void fireConsumptionEvent() { - try { - double consumption = this.consumptionAlgorithm.calculateConsumption(measurement); - double co2 = this.consumptionAlgorithm.calculateCO2FromConsumption(consumption); - - // fire the events. - mBus.post(new Co2Event(co2)); - mBus.post(new ConsumptionEvent(consumption)); - - } catch (FuelConsumptionException e) { - LOG.warn(e.getMessage()); - } catch (UnsupportedFuelTypeException e) { - if (!fuelTypeNotSupportedLogged) { - LOG.warn(e.getMessage()); - fuelTypeNotSupportedLogged = true; - } - } - - } - - public void newIntakeTemperature(int i) { - this.measurement.setProperty(Measurement.PropertyKey.INTAKE_TEMPERATURE, Double.valueOf(i)); - checkAndCreateCalculatedMAF(); - // checkStateAndPush(); - } - - public void newIntakePressure(int p) { - this.measurement.setProperty(Measurement.PropertyKey.INTAKE_PRESSURE, Double.valueOf(p)); - checkAndCreateCalculatedMAF(); - // checkStateAndPush(); - } - - public void newTPS(int tps) { - this.measurement.setProperty(Measurement.PropertyKey.THROTTLE_POSITON, Double.valueOf(tps)); - } - - public void newEngineLoad(double load) { - this.measurement.setProperty(Measurement.PropertyKey.ENGINE_LOAD, load); - } - - public void newDop(GpsDOP dop) { - if (dop.hasPdop()) { - this.measurement.setProperty(Measurement.PropertyKey.GPS_PDOP, dop.getPdop()); - } - - if (dop.hasHdop()) { - this.measurement.setProperty(Measurement.PropertyKey.GPS_HDOP, dop.getHdop()); - } - - if (dop.hasVdop()) { - this.measurement.setProperty(Measurement.PropertyKey.GPS_VDOP, dop.getVdop()); - } - } - - /** - * currently, this method is only called when a location update - * was received. as the update rate of the GPS receiver is - * lower (1 Hz probably) then the update rate of the OBD adapter - * (revised one) this provides smaller time deltas. A previous location - * update could be <= 1 second. Following this approach the delta - * is the maximum of the OBD adapter update rate. - */ - private synchronized void checkStateAndPush() { - LOG.warn("checkStateAndPush()"); - if (measurement == null) return; - if (checkReady(measurement)) { - try { - double consumption = this.consumptionAlgorithm.calculateConsumption(measurement); - double co2 = this.consumptionAlgorithm.calculateCO2FromConsumption(consumption); - this.measurement.setProperty(Measurement.PropertyKey.CONSUMPTION, consumption); - this.measurement.setProperty(Measurement.PropertyKey.CO2, co2); - } catch (FuelConsumptionException e) { - LOG.warn(e.getMessage()); - } catch (UnsupportedFuelTypeException e) { - if (!fuelTypeNotSupportedLogged) { - LOG.warn(e.getMessage()); - fuelTypeNotSupportedLogged = true; - } - } - - /* - * update the time as the latest values represent - * this measurement - */ - measurement.setTime(System.currentTimeMillis()); - LOG.warn("Try to insert Measurement."); - insertMeasurement(measurement); - - mBus.post(new NewMeasurementEvent(measurement.carbonCopy())); - resetMeasurement(); - } - } - - - private boolean checkReady(Measurement m) { - if (m.getLatitude() == 0.0 || m.getLongitude() == 0.0) return false; - - if (System.currentTimeMillis() - m.getTime() < samplingRateDelta) return false; - - if (!m.hasProperty(Measurement.PropertyKey.SPEED) || m.getProperty(Measurement.PropertyKey.SPEED) == null) - return false; - - /* - * emulate the legacy behavior: insert measurement despite data might be missing - */ - // if (m.getSpeed() == 0) return false; - // - // if (m.getCO2() == 0.0) return false; - // - // if (m.getConsumption() == 0.0) return false; - // - // if (m.getCalculatedMaf() == 0.0 || m.getMaf() == 0.0) return false; - // - // if (m.getRpm() == 0) return false; - // - // if (m.getIntakePressure() == 0) return false; - // - // if (m.getIntakeTemperature() == 0) return false; - - return true; - } - - private void insertMeasurement(Measurement m) { - LOG.warn("Insert measurement"); - callback.insertMeasurement(m.carbonCopy()); - } - - public void newFuelSystemStatus(boolean loop, int status) { - this.measurement.setProperty(Measurement.PropertyKey.FUEL_SYSTEM_LOOP, loop ? 1d : 0d); - this.measurement.setProperty(Measurement.PropertyKey.FUEL_SYSTEM_STATUS_CODE, (double) status); - } - - public void newLambdaProbeValue(O2LambdaProbe command) { - if (command instanceof O2LambdaProbeVoltage) { - this.measurement.setProperty(Measurement.PropertyKey.LAMBDA_VOLTAGE, ((O2LambdaProbeVoltage) - command).getVoltage()); - this.measurement.setProperty(Measurement.PropertyKey.LAMBDA_VOLTAGE_ER, command - .getEquivalenceRatio()); - } else if (command instanceof O2LambdaProbeCurrent) { - this.measurement.setProperty(Measurement.PropertyKey.LAMBDA_CURRENT, ((O2LambdaProbeCurrent) - command).getCurrent()); - this.measurement.setProperty(Measurement.PropertyKey.LAMBDA_CURRENT_ER, command - .getEquivalenceRatio()); - } - } - - public void newShortTermTrimBank1(Number numberResult) { - this.measurement.setProperty(Measurement.PropertyKey.SHORT_TERM_TRIM_1, numberResult.doubleValue()); - } - - public void newLongTermTrimBank1(Number numberResult) { - this.measurement.setProperty(Measurement.PropertyKey.LONG_TERM_TRIM_1, numberResult.doubleValue()); - } - - - @Subscribe - public void onReceiveLocationChangedEvent(GpsLocationChangedEvent event) { - LOG.warn(String.format("Received event: %s", event.toString())); - newLocation(event.mLocation); - } - - @Subscribe - public void onReceiveBluetoothServiceStateChangedEvent( - BluetoothServiceStateChangedEvent event) { - LOG.warn(String.format("Received event: %s", event.toString())); - - // Fix. Whenever the OBDConnection stopps, then unregister from the event bus so that - // this collector can be garbage collected and no new ghost measurement is created. - if (event.mState == BluetoothServiceState.SERVICE_STOPPING) { - mBus.unregister(this); - } - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/ConnectionListener.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/ConnectionListener.java similarity index 92% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/ConnectionListener.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/ConnectionListener.java index cc947c3fe..e857a26de 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/ConnectionListener.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/ConnectionListener.java @@ -16,12 +16,14 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.protocol; +package org.envirocar.obd; + +import org.envirocar.obd.OBDController; import java.io.IOException; /** - * Interface is used by the {@link OBDCommandLooper} to provide + * Interface is used by the {@link OBDController} to provide * information on the connection state. * * @author matthes rieke diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/FeatureFlags.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/FeatureFlags.java deleted file mode 100644 index e46474c4a..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/FeatureFlags.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -/** - * TODO make unstatic - */ -public class FeatureFlags { - - /** - * use PID supported query to identify - */ - public static final String PID_SUPPORTED_KEY = "pref_pid_supported"; - - private static SharedPreferences prefs = null; - - public FeatureFlags(Context context){ - prefs = PreferenceManager.getDefaultSharedPreferences(context); - } -// public static void init(Context context) { -// prefs = PreferenceManager.getDefaultSharedPreferences(context); -// } - - - public static boolean usePIDSupported() { - return getFlagValue(PID_SUPPORTED_KEY); - } - - private static boolean getFlagValue(String s) { - if (prefs == null) { - return false; - } - try { - return prefs.getBoolean(s, false); - } - catch (RuntimeException e) { - } - catch (Error e) { - } - - return false; - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/Listener.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/Listener.java deleted file mode 100644 index c597a6b7a..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/Listener.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd; - -import org.envirocar.obd.commands.CommonCommand; - -/** - * Interface that listens for updates from the current obd job - * - * @author jakob - * - */ - -public interface Listener { - - /** - * Receive the current command - * - * @param currentJob - * the answer-job - */ - void receiveUpdate(CommonCommand currentJob); - - void shutdown(); - - void onConnected(String deviceName); - - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/MeasurementListener.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/MeasurementListener.java deleted file mode 100644 index a3184b882..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/MeasurementListener.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd; - - -import org.envirocar.core.entity.Measurement; - -public interface MeasurementListener { - - public void insertMeasurement(Measurement m); - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/OBDController.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/OBDController.java new file mode 100644 index 000000000..4d9c5b247 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/OBDController.java @@ -0,0 +1,375 @@ +/* + * enviroCar 2013 + * Copyright (C) 2013 + * Martin Dueren, Jakob Moellers, Gerald Pape, Christopher Stephan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +package org.envirocar.obd; + +import com.google.common.base.Preconditions; +import com.squareup.otto.Bus; + +import org.envirocar.core.entity.Measurement; +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.adapter.AposW3Adapter; +import org.envirocar.obd.adapter.CarTrendAdapter; +import org.envirocar.obd.adapter.ELM327Adapter; +import org.envirocar.obd.adapter.OBDAdapter; +import org.envirocar.obd.adapter.async.DriveDeckSportAdapter; +import org.envirocar.obd.bluetooth.BluetoothSocketWrapper; +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.PIDUtil; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.events.PropertyKeyEvent; +import org.envirocar.obd.events.RPMUpdateEvent; +import org.envirocar.obd.events.SpeedUpdateEvent; +import org.envirocar.obd.exception.AllAdaptersFailedException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.TimeUnit; + +import rx.Scheduler; +import rx.Subscriber; +import rx.Subscription; +import rx.schedulers.Schedulers; + +/** + * this is the main class for interacting with a OBD-II adapter. + * It takes {@link InputStream} and {@link OutputStream} objects + * to do the actual raw communication. The {@link ConnectionListener} will get informed on + * certain changes in the connection state. + * + * @author matthes rieke + */ +public class OBDController { + private static final Logger LOG = Logger.getLogger(OBDController.class); + public static final long MAX_NODATA_TIME = 10000; + + private Subscription initSubscription; + private Subscription dataSubscription; + + private Queue adapterCandidates = new ArrayDeque<>(); + private OBDAdapter obdAdapter; + private InputStream inputStream; + private OutputStream outputStream; + private ConnectionListener connectionListener; + private String deviceName; + private boolean userRequestedStop = false; + private Bus eventBus; + private Scheduler.Worker eventBusWorker; + + /** + * Default Constructor. + * + * @param bluetoothSocketWrapper + * @param cl + * @param bus + */ + public OBDController(BluetoothSocketWrapper bluetoothSocketWrapper, ConnectionListener cl, + Bus bus) throws IOException { + this(bluetoothSocketWrapper.getInputStream(), + bluetoothSocketWrapper.getOutputStream(), + bluetoothSocketWrapper.getRemoteDeviceName(), + cl, bus); + } + + /** + * Init the OBD control layer with the streams and listeners to be used. + * + * @param in the inputStream of the connection + * @param out the outputStream of the connection + * @param cl the connection listener which receives connection state changes + */ + public OBDController(InputStream in, OutputStream out, + String deviceName, ConnectionListener cl, Bus bus) { + this.inputStream = Preconditions.checkNotNull(in); + this.outputStream = Preconditions.checkNotNull(out); + this.connectionListener = Preconditions.checkNotNull(cl); + this.deviceName = Preconditions.checkNotNull(deviceName); + + setupAdapterCandidates(); + startPreferredAdapter(); + + this.eventBus = bus; + if (this.eventBus != null) { + this.eventBusWorker = Schedulers.io().createWorker(); + } + } + + /** + * setup the list of available Adapter implementations + */ + private void setupAdapterCandidates() { + adapterCandidates.clear(); + adapterCandidates.offer(new ELM327Adapter()); + adapterCandidates.offer(new CarTrendAdapter()); + adapterCandidates.offer(new AposW3Adapter()); + adapterCandidates.offer(new DriveDeckSportAdapter()); + } + + /** + * start the preferred adapter, determined by the device name + */ + private void startPreferredAdapter() { + for (OBDAdapter ac : adapterCandidates) { + if (ac.supportsDevice(this.deviceName)) { + this.obdAdapter = ac; + break; + } + } + + if (this.obdAdapter == null) { + //poll the first instead + this.obdAdapter = adapterCandidates.poll(); + } else { + //remove the preferred from the queue so it is not used again + this.adapterCandidates.remove(this.obdAdapter); + } + + LOG.info("Using " + this.obdAdapter.getClass().getSimpleName() + " connector as the " + + "preferred adapter."); + startInitialization(false); + } + + /** + * select the next adapter candidates from the list of implementations + * + * @throws AllAdaptersFailedException if the list has reached its end + */ + private void selectNextAdapter() throws AllAdaptersFailedException { + this.obdAdapter = adapterCandidates.poll(); + + if (this.obdAdapter == null) { + throw new AllAdaptersFailedException("All candidate adapters failed"); + } + } + + /** + * start the init method of the adapter. This is used + * to bootstrap and verify the connection of the adapter + * with the ECU. + *

+ * The init times out after a pre-defined period. + */ + private void startInitialization(boolean alreadyTried) { + // start the observable and subscribe to it + this.initSubscription = this.obdAdapter.initialize(this.inputStream, this.outputStream) + .subscribeOn(Schedulers.io()) + .observeOn(OBDSchedulers.scheduler()) + .timeout(this.obdAdapter.getExpectedInitPeriod(), TimeUnit.MILLISECONDS) + .subscribe(getInitSubscriber(alreadyTried)); + } + + private Subscriber getInitSubscriber(boolean alreadyTried) { + return new Subscriber() { + + @Override + public void onCompleted() { + LOG.info("Connecting has been initialized!"); + } + + @Override + public void onError(Throwable e) { + LOG.warn("Adapter failed: " + obdAdapter.getClass().getSimpleName(), e); + try { + this.unsubscribe(); + + if (obdAdapter.hasCertifiedConnection()) { + if (!alreadyTried) { + // one retry if it was verified! + startInitialization(true); + } else { + throw new AllAdaptersFailedException( + "Adapter verified a connection but could not establishe data: " + + obdAdapter.getClass().getSimpleName()); + } + } else { + selectNextAdapter(); + + // try the selected adapter + startInitialization(false); + } + + } catch (AllAdaptersFailedException e1) { + LOG.warn("All Adapters failed", e1); + connectionListener.onAllAdaptersFailed(); + //TODO implement equivalent notification method: + //dataListener.shutdown(); + } + } + + @Override + public void onNext(Boolean b) { + LOG.info("Connection verified - starting data collection"); + + //unsubscribe, otherwise we will get a timeout + this.unsubscribe(); + + startCollectingData(); + //TODO implement equivalent notification method: + //dataListener.onConnected(deviceName); + } + + }; + } + + /** + * start the actual collection of data. + *

+ * the collection times out after a pre-defined period when no + * new data has arrived. + */ + private void startCollectingData() { + LOG.info("OBDController.startCollectingData()"); + + //inform the listener about the successful conn + this.connectionListener.onConnectionVerified(); + + // start the observable with a timeout + this.dataSubscription = this.obdAdapter.observe() + .subscribeOn(Schedulers.io()) + .observeOn(OBDSchedulers.scheduler()) + .timeout(MAX_NODATA_TIME, TimeUnit.MILLISECONDS) + .subscribe(getCollectingDataSubscriber()); + + } + + private Subscriber getCollectingDataSubscriber() { + return new Subscriber() { + @Override + public void onCompleted() { + LOG.info("onCompleted(): data collection"); + //TODO implement equivalent notification method: + //dataListener.shutdown(); + } + + @Override + public void onError(Throwable e) { + LOG.warn("onError() received", e); + + // check if this is a demanded stop: still this can lead to any kind of Exception + if (userRequestedStop) { + //TODO implement equivalent notification method: + //dataListener.shutdown(); + } + + this.unsubscribe(); + } + + @Override + public void onNext(DataResponse dataResponse) { + pushToEventBus(dataResponse); + } + }; + } + + private void pushToEventBus(DataResponse dataResponse) { + eventBusWorker.schedule(() -> { + PropertyKeyEvent[] pkes = createEventsFromDataResponse(dataResponse); + + for (PropertyKeyEvent pke : pkes) { + eventBus.post(pke); + } + + PID pid = dataResponse.getPid(); + if (pid == PID.SPEED) { + eventBus.post(new SpeedUpdateEvent(dataResponse.getValue().intValue())); + } else if (pid == PID.RPM) { + eventBus.post(new RPMUpdateEvent(dataResponse.getValue().intValue())); + } + }); + } + + protected PropertyKeyEvent[] createEventsFromDataResponse(DataResponse dataResponse) { + PID pid = dataResponse.getPid(); + switch (pid) { + case FUEL_SYSTEM_STATUS: + case CALCULATED_ENGINE_LOAD: + case SHORT_TERM_FUEL_TRIM_BANK_1: + case LONG_TERM_FUEL_TRIM_BANK_1: + case FUEL_PRESSURE: + case INTAKE_MAP: + case RPM: + case SPEED: + case INTAKE_AIR_TEMP: + case MAF: + case TPS: + return new PropertyKeyEvent[]{ + new PropertyKeyEvent(PIDUtil.toPropertyKey(pid), + dataResponse.getValue(), dataResponse.getTimestamp()) + }; + case O2_LAMBDA_PROBE_1_VOLTAGE: + case O2_LAMBDA_PROBE_2_VOLTAGE: + case O2_LAMBDA_PROBE_3_VOLTAGE: + case O2_LAMBDA_PROBE_4_VOLTAGE: + case O2_LAMBDA_PROBE_5_VOLTAGE: + case O2_LAMBDA_PROBE_6_VOLTAGE: + case O2_LAMBDA_PROBE_7_VOLTAGE: + case O2_LAMBDA_PROBE_8_VOLTAGE: + return new PropertyKeyEvent[]{ + new PropertyKeyEvent(Measurement.PropertyKey.LAMBDA_VOLTAGE_ER, + dataResponse.getCompositeValues()[0], dataResponse.getTimestamp()), + new PropertyKeyEvent(Measurement.PropertyKey.LAMBDA_VOLTAGE, + dataResponse.getCompositeValues()[1], dataResponse.getTimestamp()) + }; + case O2_LAMBDA_PROBE_1_CURRENT: + case O2_LAMBDA_PROBE_2_CURRENT: + case O2_LAMBDA_PROBE_3_CURRENT: + case O2_LAMBDA_PROBE_4_CURRENT: + case O2_LAMBDA_PROBE_5_CURRENT: + case O2_LAMBDA_PROBE_6_CURRENT: + case O2_LAMBDA_PROBE_7_CURRENT: + case O2_LAMBDA_PROBE_8_CURRENT: + return new PropertyKeyEvent[]{ + new PropertyKeyEvent(Measurement.PropertyKey.LAMBDA_CURRENT_ER, + dataResponse.getCompositeValues()[0], dataResponse.getTimestamp()), + new PropertyKeyEvent(Measurement.PropertyKey.LAMBDA_CURRENT, + dataResponse.getCompositeValues()[1], dataResponse.getTimestamp()) + }; + } + + return new PropertyKeyEvent[0]; + } + + /** + * Shutdown the controller. this removes all pending commands. + * This object is no longer executable, a new instance has to + * be created. + *

+ * Only use this if the stop is from high-level (e.g. user request) + * and NOT on any kind of exception + */ + public void shutdown() { + LOG.info("OBDController.shutdown()"); + + /** + * save that this is a stop on demand + */ + userRequestedStop = true; + + if (this.initSubscription != null) { + this.initSubscription.unsubscribe(); + } + if (this.dataSubscription != null) { + this.dataSubscription.unsubscribe(); + } + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/OBDSchedulers.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/OBDSchedulers.java new file mode 100644 index 000000000..221545066 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/OBDSchedulers.java @@ -0,0 +1,16 @@ +package org.envirocar.obd; + +import java.util.concurrent.Executors; + +import rx.Scheduler; +import rx.schedulers.Schedulers; + +public class OBDSchedulers { + + private static Scheduler instance = Schedulers.from(Executors.newCachedThreadPool()); + + public static Scheduler scheduler() { + return instance; + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/AposW3Adapter.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/AposW3Adapter.java new file mode 100644 index 000000000..9781007dd --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/AposW3Adapter.java @@ -0,0 +1,59 @@ +package org.envirocar.obd.adapter; + +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.commands.request.elm.ConfigurationCommand; +import org.envirocar.obd.commands.request.elm.DelayedConfigurationCommand; +import org.envirocar.obd.commands.request.elm.Timeout; +import org.envirocar.obd.exception.AdapterFailedException; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * Created by matthes on 03.11.15. + */ +public class AposW3Adapter extends ELM327Adapter { + + @Override + protected Queue createInitCommands() { + Queue result = new ArrayDeque<>(); + result.add(ConfigurationCommand.instance(ConfigurationCommand.Instance.RESET)); + + /** + * hack for too fast init requests, + * issue observed with Galaxy Nexus (4.3) and VW Tiguan 2013 + */ + result.add(new DelayedConfigurationCommand("AT E0", ConfigurationCommand.Instance.ECHO_OFF, false, 250)); + result.add(new DelayedConfigurationCommand("AT E0", ConfigurationCommand.Instance.ECHO_OFF, false, 250)); + result.add(ConfigurationCommand.instance(ConfigurationCommand.Instance.LINE_FEED_OFF)); + result.add(new Timeout(62)); + result.add(ConfigurationCommand.instance(ConfigurationCommand.Instance.SELECT_AUTO_PROTOCOL)); + return result; + } + + @Override + protected boolean analyzeMetadataResponse(byte[] response, BasicCommand sentCommand) throws AdapterFailedException { + if (sentCommand == null || !(sentCommand instanceof ConfigurationCommand)) { + return false; + } + + ConfigurationCommand sent = (ConfigurationCommand) sentCommand; + + if (sent.getInstance() == ConfigurationCommand.Instance.ECHO_OFF) { + String content = new String(response); + if (content.contains("OK")) { + succesfulCount++; + } + } else { + super.analyzeMetadataResponse(response, sentCommand); + } + + return succesfulCount >= 4; + } + + @Override + public boolean supportsDevice(String deviceName) { + return deviceName.contains("APOS") && deviceName.contains("OBD_W3"); + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/CarTrendAdapter.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/CarTrendAdapter.java new file mode 100644 index 000000000..61d1c4c23 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/CarTrendAdapter.java @@ -0,0 +1,207 @@ +package org.envirocar.obd.adapter; + +import android.util.Base64; + +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.commands.request.PIDCommand; +import org.envirocar.obd.exception.AdapterFailedException; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.List; +import java.util.Queue; + +public class CarTrendAdapter extends SyncAdapter { + + private static final Logger logger = Logger.getLogger(CarTrendAdapter.class); + private static final int MAX_METADATA_COUNT = 25; + private int requiredCount; + private boolean protocolFound; + private boolean identifySuccess; + private int metadataResponseCount; + private boolean connectionEstablished; + private int dataStartPosition = -1; + private Queue initializeRing; + private int ringSize; + private int initialCount; + + @Override + protected BasicCommand pollNextInitializationCommand() { + if (this.initializeRing == null) { + this.initializeRing = new ArrayDeque<>(); +// this.initializeRing.add(new EmptyCommand()); + this.initializeRing.add(new IdentifyCommand()); +// this.initializeRing.add(new EmptyCommand()); + this.initializeRing.add(new ProtocolCommand("S")); + this.initializeRing.add(new ProtocolCommand("1")); + this.initializeRing.add(new ProtocolCommand("2")); + this.initializeRing.add(new ProtocolCommand("3")); + this.initializeRing.add(new ProtocolCommand("4")); + this.initializeRing.add(new ProtocolCommand("5")); + this.initializeRing.add(new ProtocolCommand("6")); + this.initializeRing.add(new ConfigCommand("@E0")); + this.initializeRing.add(new ConfigCommand("@H0")); + + this.ringSize = this.initializeRing.size(); + } + + if (++initialCount == ringSize) { + logger.info("One cycle of config commands sent, trying another round"); + ringSize = this.initializeRing.size(); + initialCount = 0; + } + + BasicCommand next = this.initializeRing.poll(); + + if (next instanceof ProtocolCommand) { + /** + * re-add protocol selection + */ + this.initializeRing.offer(next); + } + + return next; + } + + @Override + protected List providePendingCommands() { + return super.defaultCycleCommands(); + } + + @Override + protected boolean analyzeMetadataResponse(byte[] response, BasicCommand sentCommand) throws AdapterFailedException { + logger.info("Parsing meta response: "+ Base64.encodeToString(response, Base64.DEFAULT)+ + "; sentCommand="+Base64.encodeToString(sentCommand.getOutputBytes(), Base64.DEFAULT)); + + if (response == null || response.length == 0) { + return false; + } + + String asString = new String(response).toLowerCase(); + + if (asString.contains("ms4200")) { + identifySuccess = true; + logger.info("Received Identity response. This should be a CarTrend: "+asString); + } + + if (identifySuccess && asString.contains("onnected")) { + this.connectionEstablished = true; + logger.info(String.format("Connected on Protocol %s. Adapter responded '%s'", + new String(sentCommand.getOutputBytes()), new String(response))); + } + + if (sentCommand instanceof ProtocolCommand && asString.contains("error") && asString.contains("unable")) { + /** + * the adapter understood the command but could not connect --> it is still a certified + * connection as no '?' was returned + */ + identifySuccess = true; + } + + if (++this.metadataResponseCount > MAX_METADATA_COUNT && !this.connectionEstablished) { + throw new AdapterFailedException("Too many tries. Could not establish data link"); + } + + return this.connectionEstablished; + } + + @Override + protected byte[] preProcess(byte[] bytes) { + if (dataStartPosition == -1) { + String data = new String(bytes); + /** + * search for "41" (= status ok) + */ + dataStartPosition = data.indexOf("41"); + logger.info(String.format("Identified start position %s by response '%s'", + dataStartPosition, new String(bytes))); + } + + if (dataStartPosition < bytes.length) { + return Arrays.copyOfRange(bytes, dataStartPosition, bytes.length); + } + else { + return bytes; + } + } + + @Override + public boolean supportsDevice(String deviceName) { + return deviceName.toLowerCase().contains("cartrend"); + } + + @Override + public boolean hasCertifiedConnection() { + return this.identifySuccess; + } + + @Override + public long getExpectedInitPeriod() { + return 35000; + } + + private static class ConfigCommand extends GenericCommand { + + protected ConfigCommand(String content) { + super(content); + } + + } + + private class ProtocolCommand extends GenericCommand { + + protected ProtocolCommand(String id) { + super("@P".concat(id)); + } + + @Override + public byte[] getOutputBytes() { + return CarTrendAdapter.this.protocolFound ? new byte[0] : super.getOutputBytes(); + } + + } + + private static class EmptyCommand extends GenericCommand { + + protected EmptyCommand() { + super(""); + } + + } + + private static class IdentifyCommand extends GenericCommand { + + protected IdentifyCommand() { + super("@"); + } + + } + + private static class GenericCommand implements BasicCommand { + + private final String name; + + protected GenericCommand(String content) { + this.name = content; + } + + @Override + public byte[] getOutputBytes() { + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + logger.warn(e.getMessage(), e); + } + + return this.name.getBytes(); + } + + @Override + public boolean awaitsResults() { + return true; + } + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/CommandExecutor.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/CommandExecutor.java new file mode 100644 index 000000000..319cddb6a --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/CommandExecutor.java @@ -0,0 +1,137 @@ +package org.envirocar.obd.adapter; + +import android.util.Base64; + +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.exception.StreamFinishedException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; + +import rx.Observable; +import rx.Subscriber; + +public class CommandExecutor { + + private static final Logger LOGGER = Logger.getLogger(CommandExecutor.class.getName()); + private final Set ignoredChars; + private final byte endOfLineOutput; + private final byte endOfLineInput; + private OutputStream outputStream; + private InputStream inputStream; + private ResponseQuirkWorkaround quirk; + + + public CommandExecutor(InputStream is, OutputStream os, + Set ignoredChars, Character endOfLineInput, Character endOfLineOutput) { + this.inputStream = is; + this.outputStream = os; + this.ignoredChars = new HashSet<>(ignoredChars.size()); + + for (Character c : ignoredChars) { + this.ignoredChars.add((byte) c.charValue()); + } + + this.endOfLineOutput = (byte) endOfLineOutput.charValue(); + this.endOfLineInput = (byte) endOfLineInput.charValue(); + } + + public void setQuirk(ResponseQuirkWorkaround quirk) { + this.quirk = quirk; + } + + public void execute(BasicCommand cmd) throws IOException { + if (cmd == null) { + throw new IOException("Command cannot be null!"); + } + + byte[] bytes = cmd.getOutputBytes(); + + if (LOGGER.isEnabled(Logger.DEBUG)) { + LOGGER.debug("Sending bytes: "+ new String(bytes)); + } + + // write to OutputStream, or in this case a BluetoothSocket + synchronized (this) { + outputStream.write(bytes); + outputStream.write(endOfLineOutput); + outputStream.flush(); + } + } + + public Observable createRawByteObservable() { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + while (!subscriber.isUnsubscribed()) { + byte[] bytes = readResponseLine(); + if (LOGGER.isEnabled(Logger.DEBUG)) { + LOGGER.debug("Received bytes: "+ Base64.encodeToString(bytes, Base64.DEFAULT)); + } + subscriber.onNext(bytes); + } + } catch (IOException e) { + subscriber.onError(e); + } catch (StreamFinishedException e) { + subscriber.onCompleted(); + } + } + }); + } + + + private byte[] readResponseLine() throws IOException, StreamFinishedException { + // TODO +// LOGGER.info("Reading response line..."); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // read until end of line arrives + readUntilLineEnd(baos); + + byte[] byteArray = baos.toByteArray(); + + //some adapter (i.e. the drivedeck) MIGHT respond with linebreaks as actual data - detect this + if (quirk != null && quirk.shouldWaitForNextTokenLine(byteArray)) { + LOGGER.info("Detected quirk: "+this.quirk.getClass().getSimpleName()); + + //re-add the end of line, it was dismissed previously + baos.write(this.endOfLineInput); + readUntilLineEnd(baos); + byteArray = baos.toByteArray(); + } + + if (byteArray.length > 0 && LOGGER.isEnabled(Logger.DEBUG)) { + LOGGER.debug("Received bytes: " + Base64.encodeToString(byteArray, Base64.DEFAULT)); + } + + return byteArray; + } + + private void readUntilLineEnd(ByteArrayOutputStream baos) throws IOException, StreamFinishedException { + int i = inputStream.read(); + byte b = (byte) i; + while (b != this.endOfLineInput) { + if (i == -1) { + throw new StreamFinishedException("Stream finished"); + } + + if (!ignoredChars.contains(b)){ + baos.write(b); + } + + i = inputStream.read(); + b = (byte) i; + } + } + + public byte[] retrieveLatestResponse() throws IOException, StreamFinishedException { + return readResponseLine(); + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/ELM327Adapter.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/ELM327Adapter.java new file mode 100644 index 000000000..0ab06b644 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/ELM327Adapter.java @@ -0,0 +1,114 @@ +package org.envirocar.obd.adapter; + +import android.util.Base64; + +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.commands.request.PIDCommand; +import org.envirocar.obd.commands.request.elm.ConfigurationCommand; +import org.envirocar.obd.commands.request.elm.Timeout; +import org.envirocar.obd.exception.AdapterFailedException; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Queue; + +import rx.Observable; + +/** + * Created by matthes on 02.11.15. + */ +public class ELM327Adapter extends SyncAdapter { + + private static final Logger LOG = Logger.getLogger(ELM327Adapter.class); + + private Queue initCommands; + protected int succesfulCount; + private boolean certifiedConnection; + + + @Override + protected BasicCommand pollNextInitializationCommand() { + return this.initCommands.poll(); + } + + @Override + public Observable initialize(InputStream is, OutputStream os) { + this.initCommands = createInitCommands(); + return super.initialize(is, os); + } + + protected Queue createInitCommands() { + Queue result = new ArrayDeque<>(); + result.add(ConfigurationCommand.instance(ConfigurationCommand.Instance.RESET)); + result.add(ConfigurationCommand.instance(ConfigurationCommand.Instance.ECHO_OFF)); + result.add(ConfigurationCommand.instance(ConfigurationCommand.Instance.ECHO_OFF)); + result.add(ConfigurationCommand.instance(ConfigurationCommand.Instance.LINE_FEED_OFF)); + result.add(new Timeout(62)); + result.add(ConfigurationCommand.instance(ConfigurationCommand.Instance.SELECT_AUTO_PROTOCOL)); + return result; + } + + @Override + protected List providePendingCommands() { + return super.defaultCycleCommands(); + } + + @Override + protected boolean analyzeMetadataResponse(byte[] response, BasicCommand sentCommand) throws AdapterFailedException { + String content = new String(response); + LOG.info("Analyzing metadata response: "+ Base64.encodeToString(response, Base64.DEFAULT)); + + if (sentCommand == null || !(sentCommand instanceof ConfigurationCommand)) { + return false; + } + + ConfigurationCommand sent = (ConfigurationCommand) sentCommand; + + if (sent.getInstance() == ConfigurationCommand.Instance.ECHO_OFF) { + if (content.contains("ELM327v1.") || content.contains("OK")) { + succesfulCount++; + certifiedConnection = true; + } + } + + else if (sent.getInstance() == ConfigurationCommand.Instance.LINE_FEED_OFF) { + if (content.contains("OK")) { + succesfulCount++; + } + } + + else if (sent instanceof Timeout) { + if (content.contains("OK")) { + succesfulCount++; + } + } + + else if (sent.getInstance() == ConfigurationCommand.Instance.SELECT_AUTO_PROTOCOL) { + if (content.contains("OK")) { + succesfulCount++; + } + } + + LOG.info("succesfulCount="+succesfulCount); + + return succesfulCount >= 5; + } + + @Override + protected byte[] preProcess(byte[] bytes) { + return bytes; + } + + @Override + public boolean supportsDevice(String deviceName) { + return deviceName.contains("OBDII") || deviceName.contains("ELM327") || deviceName.toLowerCase().contains("obdlink mx"); + } + + @Override + public boolean hasCertifiedConnection() { + return certifiedConnection; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/OBDAdapter.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/OBDAdapter.java new file mode 100644 index 000000000..fbd1b8b08 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/OBDAdapter.java @@ -0,0 +1,103 @@ +/* + * enviroCar 2013 + * Copyright (C) 2013 + * Martin Dueren, Jakob Moellers, Gerald Pape, Christopher Stephan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +package org.envirocar.obd.adapter; + +import org.envirocar.obd.commands.response.DataResponse; + +import java.io.InputStream; +import java.io.OutputStream; + +import rx.Observable; + +/** + * Interface for a OBD connector. It can provide device specific + * command requests and initialization sequences. + * + * @author matthes rieke + * + */ +public interface OBDAdapter { + + + enum ConnectionState { + + /** + * used to indicate a state when the connector could + * not understand any response received + */ + DISCONNECTED, + + /** + * used to indicate a state when the connector understood + * at least one command. Return this state only if the + * adapter is sure, that it can interact with the device + * - but the device yet did not return measurements + */ + CONNECTED, + + /** + * used to indicate a state where the connector received + * a parseable measurement + */ + VERIFIED + } + + /** + * Initialize the connection. + * + * @param is the inpusttream + * @param os this outputstream + * @return an observable that calls onNext on successful connection + */ + Observable initialize(InputStream is, OutputStream os); + + /** + * Start the actual data collection + * + * @return an observable that provides data responses + */ + Observable observe(); + + /** + * An implementation shall return true if it + * might support the given bluetooth device. + * + * @param deviceName the bluetooth device name + * @return if it suggests support for the device + */ + boolean supportsDevice(String deviceName); + + /** + * This method is used to decide if another adapter implementation is + * worth a try. If an adapter verified a connection (e.g. via special metadata + * responses) and is sure that it is the correct adapter, it shall return + * true. Then no other adapter will be tried in order to do not waste time. + * + * @return true if the adapter has determined a compatible device + */ + boolean hasCertifiedConnection(); + + /** + * + * @return the time (in ms) the adapter might take to connect to the OBD layer + */ + long getExpectedInitPeriod(); +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/ResponseQuirkWorkaround.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/ResponseQuirkWorkaround.java new file mode 100644 index 000000000..b0db16396 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/ResponseQuirkWorkaround.java @@ -0,0 +1,7 @@ +package org.envirocar.obd.adapter; + +public interface ResponseQuirkWorkaround { + + boolean shouldWaitForNextTokenLine(byte[] byteArray); + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/SyncAdapter.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/SyncAdapter.java new file mode 100644 index 000000000..b49265d2e --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/SyncAdapter.java @@ -0,0 +1,289 @@ +package org.envirocar.obd.adapter; + +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.PIDSupported; +import org.envirocar.obd.commands.PIDUtil; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.commands.request.PIDCommand; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.commands.response.ResponseParser; +import org.envirocar.obd.exception.AdapterFailedException; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.StreamFinishedException; +import org.envirocar.obd.exception.UnmatchedResponseException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import rx.Observable; +import rx.Subscriber; + +public abstract class SyncAdapter implements OBDAdapter { + + private static final Logger LOGGER = Logger.getLogger(SyncAdapter.class.getName()); + + protected static final long ADAPTER_TRY_PERIOD = 20000; + + private static final char COMMAND_SEND_END = '\r'; + private static final char COMMAND_RECEIVE_END = '>'; + private static final char COMMAND_RECEIVE_SPACE = ' '; + private static final int MAX_ERROR_PER_COMMAND = 5; + + private Set ignoredChars = new HashSet<>(Arrays.asList(COMMAND_RECEIVE_SPACE, COMMAND_SEND_END)); + private CommandExecutor commandExecutor; + private ResponseParser parser = new ResponseParser(); + + private Set supportedPIDs = new HashSet<>(); + + private Map failureMap = new HashMap<>(); + private List requestCommands; + private Queue commandRingBuffer = new ArrayDeque<>(); + private Queue pidSupportedCommands = new ArrayDeque<>(Arrays.asList(new PIDSupported[] {new PIDSupported(), new PIDSupported("20")})); + + + @Override + public Observable initialize(InputStream is, OutputStream os) { + commandExecutor = new CommandExecutor(is, os, ignoredChars, COMMAND_RECEIVE_END, COMMAND_SEND_END); + + /** + * create an observable that tries to verify the + * connection based on response analysis + */ + Observable obs = Observable.create(new Observable.OnSubscribe() { + + @Override + public void call(Subscriber subscriber) { + try { + boolean analyzedSuccessfully = false; + + while (!subscriber.isUnsubscribed()) { + if (analyzedSuccessfully) { + /** + * a successful data connection has been established: + * retrieve the supported PIDs + */ + PIDSupported pid = pidSupportedCommands.poll(); + while (pid != null) { + commandExecutor.execute(pid); + byte[] resp = commandExecutor.retrieveLatestResponse(); + try { + supportedPIDs.addAll(pid.parsePIDs(resp)); + } catch (InvalidCommandResponseException | NoDataReceivedException + | UnmatchedResponseException | AdapterSearchingException e) { + LOGGER.warn(e.getMessage(), e); + } + LOGGER.info("Currently supported PIDs: " + supportedPIDs.toString()); + pid = pidSupportedCommands.poll(); + } + + subscriber.onNext(true); + subscriber.unsubscribe(); + } + else { + BasicCommand cc = pollNextInitializationCommand(); + + if (cc == null) { + subscriber.onError(new AdapterFailedException( + "All init commands sent, but could not verify connection")); + subscriber.unsubscribe(); + } + + //push the command to the output stream + commandExecutor.execute(cc); + + //check if the command needs a response (most likely) + if (cc.awaitsResults()) { + byte[] resp = commandExecutor.retrieveLatestResponse(); + analyzedSuccessfully = analyzeMetadataResponse(resp, cc); + } + } + } + } catch (IOException | AdapterFailedException e) { + subscriber.onError(e); + subscriber.unsubscribe(); + } catch (StreamFinishedException e) { + LOGGER.warn("The stream was closed unexpectedly: "+e.getMessage()); + subscriber.onCompleted(); + subscriber.unsubscribe(); + } + } + }); + + return obs; + } + + @Override + public Observable observe() { + return Observable.create(new Observable.OnSubscribe() { + + @Override + public void call(Subscriber subscriber) { + LOGGER.info("SyncAdapter.observe().call()"); + + //prepare all pending data commands + preparePendingCommands(); + + PIDCommand latestCommand = null; + byte[] bytes = null; + while (!subscriber.isUnsubscribed()) { + try { + latestCommand = pollNextCommand(); + LOGGER.debug("Sending command " + (latestCommand != null ? latestCommand.getPid().toString() : "n/a")); + + /** + * write the next pending command + */ + if (latestCommand != null) { + commandExecutor.execute(latestCommand); + } + + /** + * read the next incoming response + */ + bytes = commandExecutor.retrieveLatestResponse(); + + DataResponse response = parser.parse(preProcess(bytes)); + + if (response != null) { + LOGGER.debug("isUnsubscribed? " + subscriber.isUnsubscribed()); + subscriber.onNext(response); + } + } catch (IOException e) { + subscriber.onError(e); + subscriber.unsubscribe(); + } catch (AdapterFailedException e) { + subscriber.onError(e); + subscriber.unsubscribe(); + } catch (StreamFinishedException e) { + LOGGER.info("Stream finished: "+ e.getMessage()); + subscriber.onCompleted(); + subscriber.unsubscribe(); + } catch (AdapterSearchingException e) { + LOGGER.warn("Adapter still searching: " + e.getMessage()); + } catch (NoDataReceivedException e) { + LOGGER.warn("No data received: " + e.getMessage()); + increaseFailureCount(latestCommand.getPid()); + } catch (InvalidCommandResponseException e) { + LOGGER.warn("Received InvalidCommandResponseException: " + e.getCommand()); + increaseFailureCount(PIDUtil.fromString(e.getCommand())); + } catch (UnmatchedResponseException e) { + LOGGER.warn("Unmatched response: " + e.getMessage()); + } + } + + } + }); + } + + protected PIDCommand pollNextCommand() throws AdapterFailedException { + if (this.commandRingBuffer.isEmpty()) { + throw new AdapterFailedException("No available commands left in the buffer"); + } + + PIDCommand cmd = commandRingBuffer.poll(); + + if (cmd != null) { + if (!checkIsBlacklisted(cmd.getPid())) { + /** + * not blacklisted: add it back as the last element of the ring + */ + commandRingBuffer.offer(cmd); + return cmd; + } + else { + /** + * blacklisted: do not re-add it and return the next candidate + */ + return pollNextCommand(); + } + } + + return cmd; + } + + protected void increaseFailureCount(PID command) { + if (command == null) { + return; + } + + if (this.failureMap.containsKey(command)) { + this.failureMap.get(command).getAndIncrement(); + } + else { + AtomicInteger ai = new AtomicInteger(1); + this.failureMap.put(command, ai); + } + } + + protected List defaultCycleCommands() { + if (requestCommands == null) { + requestCommands = new ArrayList<>(); + + for (PID p: PID.values()) { + addIfSupported(p); + } + + } + + return requestCommands; + } + + protected void addIfSupported(PID pid) { + if (supportedPIDs == null || supportedPIDs.isEmpty()) { + requestCommands.add(PIDUtil.instantiateCommand(pid)); + } + else if (supportedPIDs.contains(pid)) { + requestCommands.add(PIDUtil.instantiateCommand(pid)); + } + else { + LOGGER.info("PID "+pid.toString()+" not supported. Skipping."); + } + } + + private void preparePendingCommands() { + commandRingBuffer = new ArrayDeque<>(); + + for (PIDCommand cmd : providePendingCommands()) { + if (cmd != null) { + commandRingBuffer.offer(cmd); + } + } + } + + private boolean checkIsBlacklisted(PID pid) { + return this.failureMap.containsKey(pid) && this.failureMap.get(pid).get() > MAX_ERROR_PER_COMMAND; + } + + @Override + public long getExpectedInitPeriod() { + return ADAPTER_TRY_PERIOD; + } + + protected abstract BasicCommand pollNextInitializationCommand(); + + protected abstract List providePendingCommands(); + + /** + * + * @param response the raw data received + * @param sentCommand the originating command + * @return true if the adapter established a meaningful connection + */ + protected abstract boolean analyzeMetadataResponse(byte[] response, BasicCommand sentCommand) throws AdapterFailedException; + + protected abstract byte[] preProcess(byte[] bytes); +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/AsyncAdapter.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/AsyncAdapter.java new file mode 100644 index 000000000..f7966d2b2 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/AsyncAdapter.java @@ -0,0 +1,229 @@ +package org.envirocar.obd.adapter.async; + +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.adapter.CommandExecutor; +import org.envirocar.obd.adapter.OBDAdapter; +import org.envirocar.obd.adapter.ResponseQuirkWorkaround; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.commands.response.entity.LambdaProbeVoltageResponse; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.StreamFinishedException; +import org.envirocar.obd.exception.UnmatchedResponseException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; + +/** + * Created by matthes on 03.11.15. + */ +public abstract class AsyncAdapter implements OBDAdapter { + + private static Logger LOGGER = Logger.getLogger(AsyncAdapter.class); + + private static final long DEFAULT_NO_DATA_TIMEOUT = 15000; //*10 for debug + private final char endOfLineOutput; + private final char endOfLineInput; + private CommandExecutor commandExecutor; + private Subscription dataObservable; + private AtomicBoolean quirkDisabled = new AtomicBoolean(false); + + public AsyncAdapter(char endOfLineOutput, char endOfLineInput) { + this.endOfLineOutput = endOfLineOutput; + this.endOfLineInput = endOfLineInput; + } + + /** + * use this method to disable a CommandExecutor quirk. + * @see {@link #getQuirk()} + */ + public void disableQuirk() { + if (!quirkDisabled.getAndSet(true) && commandExecutor != null) { + commandExecutor.setQuirk(null); + } + } + + @Override + public Observable initialize(InputStream is, OutputStream os) { + this.commandExecutor = new CommandExecutor(is, os, Collections.emptySet(), this.endOfLineInput, this.endOfLineOutput); + this.commandExecutor.setQuirk(getQuirk()); + + /** + * + */ + Observable observable = Observable.create(new Observable.OnSubscribe() { + @Override + public void call(final Subscriber subscriber) { + while (!subscriber.isUnsubscribed()) { + /** + * poll the next possible command + */ + BasicCommand cmd = pollNextCommand(); + if (cmd != null) { + try { + commandExecutor.execute(cmd); + } catch (IOException e) { + subscriber.onError(e); + subscriber.unsubscribe(); + } + } + + try { + byte[] response = commandExecutor.retrieveLatestResponse(); + + processResponse(response); + + if (hasEstablishedConnection()) { + subscriber.onNext(true); + subscriber.onCompleted(); + } + + } catch (IOException e) { + subscriber.onError(e); + subscriber.unsubscribe(); + } catch (StreamFinishedException e) { + subscriber.onError(e); + subscriber.unsubscribe(); + } catch (InvalidCommandResponseException e) { + LOGGER.warn(e.getMessage(), e); + } catch (NoDataReceivedException e) { + LOGGER.warn(e.getMessage(), e); + } catch (UnmatchedResponseException e) { + LOGGER.warn(e.getMessage(), e); + } catch (AdapterSearchingException e) { + LOGGER.warn(e.getMessage(), e); + } + } + } + }); + + return observable; + } + + protected abstract boolean hasEstablishedConnection(); + + + /** + * an implementation can provide a quirk for response parsing/filtering + * + * @return a quirk that filters a line of raw data + */ + protected abstract ResponseQuirkWorkaround getQuirk(); + + protected Observable createDataObservable() { + + Observable dataObservable = Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + + while (!subscriber.isUnsubscribed()) { + /** + * poll the next possible command + */ + BasicCommand cmd = pollNextCommand(); + if (cmd != null) { + try { + commandExecutor.execute(cmd); + } catch (IOException e) { + subscriber.onError(e); + subscriber.unsubscribe(); + } + } + + /** + * read the inputstream byte by byte + */ + try { + byte[] bytes = commandExecutor.retrieveLatestResponse(); + + try { + DataResponse result = processResponse(bytes); + + /** + * call our subscriber! + */ + if (result != null) { + subscriber.onNext(result); + + if (LOGGER.isEnabled(Logger.DEBUG)) { + if (result instanceof LambdaProbeVoltageResponse) { + LOGGER.debug("Received lambda voltage: "+result); + } + } + } + } catch (AdapterSearchingException e) { + LOGGER.warn("Adapter still searching: " + e.getMessage()); + } catch (NoDataReceivedException e) { + LOGGER.warn("No data received: " + e.getMessage()); + } catch (InvalidCommandResponseException e) { + LOGGER.warn("InvalidCommandResponseException: " + e.getMessage()); + } catch (UnmatchedResponseException e) { + LOGGER.warn("Unmatched response: " + e.getMessage()); + } + + } catch (IOException e) { + /** + * IOException signals broken connection, + * notify subscriber accordingly + */ + subscriber.onError(e); + return; + } catch (StreamFinishedException e) { + /** + * the stream has ended, notify the subscriber + */ + LOGGER.info("The stream was closed: "+e.getMessage()); + subscriber.onCompleted(); + return; + } + } + + subscriber.onCompleted(); + } + }).timeout(DEFAULT_NO_DATA_TIMEOUT, TimeUnit.MILLISECONDS); + + return dataObservable; + } + + @Override + public Observable observe() { + return createDataObservable(); + } + + /** + * an async adapter might want to send commands + * out irregularly or on startup. An implementation + * can provided a command that should be executed using + * this method. + * + * The returned command gets executed after a valid line of + * response has been read. + * + * @return the command or null if there is no pending + */ + protected abstract BasicCommand pollNextCommand(); + + + /** + * Parse a line of response + * + * @param bytes the bytes + * @return a command instace + * @throws InvalidCommandResponseException + * @throws NoDataReceivedException + * @throws UnmatchedResponseException + * @throws AdapterSearchingException + */ + protected abstract DataResponse processResponse(byte[] bytes) throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException; + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/ObdReset.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/CarriageReturnCommand.java similarity index 62% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/commands/ObdReset.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/CarriageReturnCommand.java index 24f2d009e..1396da4f3 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/ObdReset.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/CarriageReturnCommand.java @@ -16,27 +16,29 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.commands; +package org.envirocar.obd.adapter.async; +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.commands.request.BasicCommand; -/** - * This method will reset the OBD connection. - */ -public class ObdReset extends StringResultCommand { +public class CarriageReturnCommand implements BasicCommand { - public ObdReset() { - super("AT Z"); - } + private static final Logger LOG = Logger.getLogger(CarriageReturnCommand.class); @Override - public boolean awaitsResults() { - return false; - } + public byte[] getOutputBytes() { + try { + Thread.sleep(750); + } catch (InterruptedException e) { + LOG.warn(e.getMessage(), e); + } - @Override - public String getCommandName() { - return "Reset OBD"; + return new byte[0]; } -} \ No newline at end of file + @Override + public boolean awaitsResults() { + return true; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/CycleCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/CycleCommand.java new file mode 100644 index 000000000..0c68e0379 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/CycleCommand.java @@ -0,0 +1,170 @@ +/** + * Copyright (C) 2013 - 2015 the enviroCar community + * + * This file is part of the enviroCar app. + * + * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ +package org.envirocar.obd.adapter.async; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.request.BasicCommand; + +import java.util.List; + +public class CycleCommand implements BasicCommand { + + private byte[] bytes; + + public static enum DriveDeckPID implements DriveDeckPIDEnumInstance { + ENGINE_LOAD { + @Override + public byte getByteRepresentation() { + return convert(PID.CALCULATED_ENGINE_LOAD.getHexadecimalRepresentation()); + } + }, + SPEED { + @Override + public byte getByteRepresentation() { + return convert(PID.SPEED.getHexadecimalRepresentation()); + } + }, + RPM { + @Override + public byte getByteRepresentation() { + return convert(PID.RPM.getHexadecimalRepresentation()); + } + }, + IAP { + @Override + public byte getByteRepresentation() { + return convert(PID.INTAKE_MAP.getHexadecimalRepresentation()); + } + }, + IAT { + @Override + public byte getByteRepresentation() { + return convert(PID.INTAKE_AIR_TEMP.getHexadecimalRepresentation()); + } + }, + MAF { + @Override + public byte getByteRepresentation() { + return convert(PID.MAF.getHexadecimalRepresentation()); + } + }, + TPS { + @Override + public byte getByteRepresentation() { + return convert(PID.TPS.getHexadecimalRepresentation()); + } + }, + O2_LAMBDA_PROBE_1_VOLTAGE { + @Override + public byte getByteRepresentation() { + return convert(PID.O2_LAMBDA_PROBE_1_VOLTAGE.getHexadecimalRepresentation()); + } + }, + O2_LAMBDA_PROBE_1_CURRENT { + @Override + public byte getByteRepresentation() { + return convert(PID.O2_LAMBDA_PROBE_1_CURRENT.getHexadecimalRepresentation()); + } + }; + + protected byte convert(String pidHex) { + int by13 = incrementBy13(hexToInt(pidHex)); + return (byte) by13; + } + + protected int hexToInt(String string) { + return Integer.valueOf(string, 16); + } + + protected int incrementBy13(int hexToInt) { + return hexToInt + 13; + } + + public static DriveDeckPID fromDefaultPID(PID p) { + switch (p) { + case SHORT_TERM_FUEL_TRIM_BANK_1: + case LONG_TERM_FUEL_TRIM_BANK_1: + case FUEL_PRESSURE: + case FUEL_SYSTEM_STATUS: + return null; + case CALCULATED_ENGINE_LOAD: + return DriveDeckPID.ENGINE_LOAD; + case INTAKE_MAP: + return DriveDeckPID.IAP; + case RPM: + return DriveDeckPID.RPM; + case SPEED: + return DriveDeckPID.SPEED; + case INTAKE_AIR_TEMP: + return DriveDeckPID.IAT; + case MAF: + return DriveDeckPID.MAF; + case TPS: + return DriveDeckPID.TPS; + case O2_LAMBDA_PROBE_1_VOLTAGE: + return DriveDeckPID.O2_LAMBDA_PROBE_1_VOLTAGE; + case O2_LAMBDA_PROBE_1_CURRENT: + return DriveDeckPID.O2_LAMBDA_PROBE_1_CURRENT; + case O2_LAMBDA_PROBE_2_VOLTAGE: + case O2_LAMBDA_PROBE_3_VOLTAGE: + case O2_LAMBDA_PROBE_4_VOLTAGE: + case O2_LAMBDA_PROBE_5_VOLTAGE: + case O2_LAMBDA_PROBE_6_VOLTAGE: + case O2_LAMBDA_PROBE_7_VOLTAGE: + case O2_LAMBDA_PROBE_8_VOLTAGE: + case O2_LAMBDA_PROBE_2_CURRENT: + case O2_LAMBDA_PROBE_3_CURRENT: + case O2_LAMBDA_PROBE_4_CURRENT: + case O2_LAMBDA_PROBE_5_CURRENT: + case O2_LAMBDA_PROBE_6_CURRENT: + case O2_LAMBDA_PROBE_7_CURRENT: + case O2_LAMBDA_PROBE_8_CURRENT: + return null; + } + + return null; + } + } + + + public CycleCommand(List pidList) { + bytes = new byte[3 + pidList.size()]; + byte[] prefix = "a17".getBytes(); + + for (int i = 0; i < prefix.length; i++) { + bytes[i] = prefix[i]; + } + + int i = 0; + for (DriveDeckPID pid : pidList) { + bytes[prefix.length + i++] = pid.getByteRepresentation(); + } + } + + @Override + public byte[] getOutputBytes() { + return bytes; + } + + @Override + public boolean awaitsResults() { + return true; + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/DriveDeckPIDEnumInstance.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/DriveDeckPIDEnumInstance.java new file mode 100644 index 000000000..27a95cc21 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/DriveDeckPIDEnumInstance.java @@ -0,0 +1,10 @@ +package org.envirocar.obd.adapter.async; + +/** + * Created by matthes on 03.11.15. + */ +public interface DriveDeckPIDEnumInstance { + + byte getByteRepresentation(); + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/DriveDeckSportAdapter.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/DriveDeckSportAdapter.java new file mode 100644 index 000000000..a0ef5e33a --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/DriveDeckSportAdapter.java @@ -0,0 +1,460 @@ +/* + * enviroCar 2013 + * Copyright (C) 2013 + * Martin Dueren, Jakob Moellers, Gerald Pape, Christopher Stephan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +package org.envirocar.obd.adapter.async; + +import android.util.Base64; + +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.adapter.ResponseQuirkWorkaround; +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.PIDSupported; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.UnmatchedResponseException; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Queue; +import java.util.Set; + +public class DriveDeckSportAdapter extends AsyncAdapter { + + private static final Logger logger = Logger.getLogger(DriveDeckSportAdapter.class); + private int pidSupportedResponsesParsed; + private int connectingMessageCount; + private int totalResponseCount; + private boolean supportForLambdaVoltage = true; + + private static enum Protocol { + CAN11500, CAN11250, CAN29500, CAN29250, KWP_SLOW, KWP_FAST, ISO9141 + } + + public static final char CARRIAGE_RETURN = '\r'; + public static final char END_OF_LINE_RESPONSE = '>'; + + private static final char RESPONSE_PREFIX_CHAR = 'B'; + private static final char CYCLIC_TOKEN_SEPARATOR_CHAR = '<'; + private static final long SEND_CYCLIC_COMMAND_DELTA = 60000; + + private Protocol protocol; + private String vin; + private BasicCommand cycleCommand; + public long lastCyclicCommandSent; + private Set loggedPids = new HashSet<>(); + private org.envirocar.obd.commands.response.ResponseParser parser = new org.envirocar.obd.commands.response.ResponseParser(); + private Queue pendingCommands; + private Set supportedPIDs = new HashSet<>(); + + + public DriveDeckSportAdapter() { + super(CARRIAGE_RETURN, END_OF_LINE_RESPONSE); + + this.pendingCommands = new ArrayDeque<>(); + this.pendingCommands.offer(new CarriageReturnCommand()); + } + + @Override + protected ResponseQuirkWorkaround getQuirk() { + return new PIDSupportedQuirk(); + } + + private void createAndSendCycleCommand() { + List pidList = new ArrayList<>(); + + for (PID p: PID.values()) { + addIfSupported(p, pidList); + } + + this.cycleCommand = new CycleCommand(pidList); + logger.info("Static Cycle Command: " + Base64.encodeToString(this.cycleCommand.getOutputBytes(), Base64.DEFAULT)); + this.pendingCommands.offer(this.cycleCommand); + + /** + * as the default we will parse ASCII-PID Response '4D' to Lambda Voltage. + * If Lambda Current was found, use that instead + */ + if (supportedPIDs == null || supportedPIDs.isEmpty()) { + supportForLambdaVoltage = true; + } + else if (supportedPIDs.contains(PID.O2_LAMBDA_PROBE_1_VOLTAGE)) { + supportForLambdaVoltage = false; + } + else { + supportForLambdaVoltage = true; + } + } + + + private void addIfSupported(PID pid, List pidList) { + CycleCommand.DriveDeckPID driveDeckPID = CycleCommand.DriveDeckPID.fromDefaultPID(pid); + + if (driveDeckPID == null) { + logger.info("No DriveDeck equivalent for PID: "+pid); + return; + } + + if (supportedPIDs == null || supportedPIDs.isEmpty()) { + pidList.add(driveDeckPID); + } + else if (supportedPIDs.contains(pid)) { + pidList.add(driveDeckPID); + } + else { + logger.info("PID "+pid+" not supported. Skipping."); + } + } + + private void processDiscoveredControlUnits(String substring) { + logger.info("Discovered CUs... "); + } + + protected void processSupportedPID(byte[] bytes) throws InvalidCommandResponseException, NoDataReceivedException, + UnmatchedResponseException, AdapterSearchingException { + + logger.info("PID Supported response: " + Base64.encodeToString(bytes, Base64.DEFAULT)); + + if (bytes.length < 14) { + logger.info("PID Supported response to small: "+bytes.length); + return; + } + + /** + * check for group 00 + */ + String group = new String(new byte[]{bytes[6], bytes[7]}); + PIDSupported pidCmd = new PIDSupported(group); + byte[] rawBytes = new byte[12]; + rawBytes[0] = '4'; + rawBytes[1] = '1'; + rawBytes[2] = (byte) pidCmd.getGroup().charAt(0); + rawBytes[3] = (byte) pidCmd.getGroup().charAt(1); + int target = 4; + String hexTmp; + for (int i = 9; i < bytes.length; i++) { + if (i == 11) continue; + hexTmp = oneByteToHex(bytes[i]); + rawBytes[target++] = (byte) hexTmp.charAt(0); + rawBytes[target++] = (byte) hexTmp.charAt(1); + } + + this.supportedPIDs.addAll(pidCmd.parsePIDs(rawBytes)); + pidSupportedResponsesParsed++; + + logger.info("Supported PIDs: "+ this.supportedPIDs); + } + + private String oneByteToHex(byte b) { + String result = Integer.toString(b & 0xff, 16).toUpperCase(Locale.US); + if (result.length() == 1) result = "0".concat(result); + return result; + } + + private void processVIN(String vinInt) { + this.vin = vinInt; + logger.info("VIN is: " + this.vin); + } + + private void determineProtocol(String protocolInt) { + if (protocolInt == null || protocolInt.trim().isEmpty()) { + return; + } + + int prot; + try { + prot = Integer.parseInt(protocolInt); + } catch (NumberFormatException e) { + logger.warn("NFE: " + e.getMessage()); + return; + } + + switch (prot) { + case 1: + protocol = Protocol.CAN11500; + break; + case 2: + protocol = Protocol.CAN11250; + break; + case 3: + protocol = Protocol.CAN29500; + break; + case 4: + protocol = Protocol.CAN29250; + break; + case 5: + protocol = Protocol.KWP_SLOW; + break; + case 6: + protocol = Protocol.KWP_FAST; + break; + case 7: + protocol = Protocol.ISO9141; + break; + default: + return; + } + + logger.info("Protocol is: " + protocol.toString()); + } + + + @Override + public boolean supportsDevice(String deviceName) { + return deviceName != null && deviceName.toLowerCase().contains("drivedeck") && deviceName.toLowerCase().contains("w4"); + } + + @Override + public boolean hasCertifiedConnection() { + /** + * this is a drivedeck if a VIN response was parsed OR the protocol was communicated + * OR the adapter reported the "CONNECTED" state more than x times (unlikely to be a + * mistaken other adapter) + */ + int x = 4; + return vin != null || protocol != null || connectingMessageCount > x; + } + + @Override + protected boolean hasEstablishedConnection() { + return vin != null || protocol != null; + } + + @Override + public long getExpectedInitPeriod() { + return 30000; + } + + protected Set getSupportedPIDs() { + return supportedPIDs; + } + + private DataResponse parsePIDResponse(String pid, byte[] rawBytes) throws InvalidCommandResponseException, NoDataReceivedException, + UnmatchedResponseException, AdapterSearchingException { + + logger.verbose(String.format("PID Response: %s; %s", pid, Base64.encodeToString(rawBytes, Base64.DEFAULT)).trim()); + + /* + * resulting HEX values are 0x0d additive to the + * default PIDs of OBD. e.g. RPM = 0x19 = 0x0c + 0x0d + */ + PID result = null; + if (pid.equals("41")) { + //Speed + result = PID.SPEED; + } else if (pid.equals("42")) { + //MAF + result = PID.MAF; + } else if (pid.equals("52")) { + //IAP + result = PID.INTAKE_MAP; + } else if (pid.equals("49")) { + //IAT + result = PID.INTAKE_AIR_TEMP; + } else if (pid.equals("40")) { + //RPM + result = PID.RPM; + } else if (pid.equals("51")) { + //RPM special case: data is stored in bytes 2, 3 + result = PID.RPM; + rawBytes[0] = rawBytes[2]; + rawBytes[1] = rawBytes[3]; + } else if (pid.equals("44")) { + result = PID.TPS; + } else if (pid.equals("45")) { + result = PID.CALCULATED_ENGINE_LOAD; + } else if (pid.equals("4D")) { + //lambda probe + if (supportForLambdaVoltage) { + result = PID.O2_LAMBDA_PROBE_1_VOLTAGE; + } + else { + result = PID.O2_LAMBDA_PROBE_1_CURRENT; + } + + /** + * DriveDeck stores voltage bytes (C, D) in bytes 4, 5 (TODO: Check!) + */ + rawBytes[2] = rawBytes[4]; + rawBytes[3] = rawBytes[5]; + } else if (pid.equals("DUMMY")) { + //TODO: implement Engine Load, TPS, others + } + + oneTimePIDLog(pid, rawBytes); + + if (result != null) { + byte[] rawData = createRawData(rawBytes, result.getHexadecimalRepresentation()); + DataResponse parsed = parser.parse(rawData); + return parsed; + } + + return null; + } + + private void oneTimePIDLog(String pid, byte[] rawBytes) { + if (pid == null || rawBytes == null || rawBytes.length == 0) + return; + + if (!loggedPids.contains(pid)) { + logger.info("First response for PID: " + pid + "; Base64: " + + Base64.encodeToString(rawBytes, Base64.DEFAULT)); + loggedPids.add(pid); + } + } + + private byte[] createRawData(byte[] rawBytes, String type) { + byte[] result = new byte[4 + rawBytes.length * 2]; + byte[] typeBytes = type.getBytes(); + result[0] = (byte) '4'; + result[1] = (byte) '1'; + result[2] = typeBytes[0]; + result[3] = typeBytes[1]; + for (int i = 0; i < rawBytes.length; i++) { + String hex = oneByteToHex(rawBytes[i]); + result[(i * 2) + 4] = (byte) hex.charAt(0); + result[(i * 2) + 1 + 4] = (byte) hex.charAt(1); + } + return result; + } + + + @Override + protected BasicCommand pollNextCommand() { + BasicCommand result = this.pendingCommands.poll(); + + /** + * send the cycle command once in a while to keep the connection alive + * TODO: is this required? + */ + if (result == null && this.protocol != null && System.currentTimeMillis() - lastCyclicCommandSent > SEND_CYCLIC_COMMAND_DELTA) { + lastCyclicCommandSent = System.currentTimeMillis(); + result = this.cycleCommand; + } + + if (result != null && result == this.cycleCommand) { + logger.info("Sending Cyclic command to DriveDeck - data should be received now"); + } + + return result; + } + + @Override + protected DataResponse processResponse(byte[] bytes) throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + if (bytes.length <= 0) { + return null; + } + + char type = (char) bytes[0]; + + if (type == RESPONSE_PREFIX_CHAR) { + if (bytes.length < 3) { + logger.warn("Received a response with too less bytes. length="+bytes.length); + return null; + } + + String pid = new String(bytes, 1, 2); + + /* + * METADATA Stuff + */ + if (pid.equals("14")) { + logger.debug("Status: CONNECTING"); + connectingMessageCount++; + } else if (pid.equals("15")) { + processVIN(new String(bytes, 3, bytes.length - 3)); + } else if (pid.equals("70")) { + processSupportedPID(bytes); + } else if (pid.equals("71")) { + processDiscoveredControlUnits(new String(bytes, 3, bytes.length - 3)); + } else if (pid.equals("31")) { + // engine on + logger.debug("Engine: On"); + } else if (pid.equals("32")) { + // engine off (= RPM < 500) + logger.debug("Engine: Off"); + } else { + if (bytes.length < 6) { + throw new NoDataReceivedException("the response did only contain " + bytes.length + " bytes. For PID " + + "responses 6 are minimum"); + } + + if ((char) bytes[4] == CYCLIC_TOKEN_SEPARATOR_CHAR) { + return null; + } + + /* + * A PID response + */ + super.disableQuirk(); + + byte[] pidResponseValue = new byte[6]; + int target = 0; + for (int i = 4; i < bytes.length; i++) { + if (target >= pidResponseValue.length) { + break; + } + + if ((char) bytes[i] == CYCLIC_TOKEN_SEPARATOR_CHAR) { + continue; + } + + pidResponseValue[target++] = bytes[i]; + } + + DataResponse result = parsePIDResponse(pid, pidResponseValue); + + return result; + } + + /** + * if the protocol has been determined, wait a fair amount of responses + * to ensure that all PIDSupported were reported + */ + if (this.protocol != null) { + this.totalResponseCount++; + checkForCycleCommandCreation(); + } + + + } else if (type == 'C') { + determineProtocol(new String(bytes, 1, bytes.length - 1)); + } + + return null; + } + + private void checkForCycleCommandCreation() { + /** + * it might be the case that PID supported responses do not come in order (eg group 40 + * before 20). So we wait for a few idle responses before going to real-time mode + */ + if (pidSupportedResponsesParsed > 0 && totalResponseCount > 7 && this.cycleCommand == null) { + logger.info("Received PID supported responses and enough responses to start pulling data. Creating cycle command"); + createAndSendCycleCommand(); + } + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/LegacyCycleCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/LegacyCycleCommand.java new file mode 100644 index 000000000..7b995bd7b --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/LegacyCycleCommand.java @@ -0,0 +1,127 @@ +/** + * Copyright (C) 2013 - 2015 the enviroCar community + * + * This file is part of the enviroCar app. + * + * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ +package org.envirocar.obd.adapter.async; + +import org.envirocar.obd.commands.request.BasicCommand; + +import java.util.List; + +public class LegacyCycleCommand implements BasicCommand { + + + public static enum PID { + SPEED { + @Override + public String toString() { + return convert("0D"); + } + }, + MAF { + @Override + public String toString() { + return convert("10"); + } + }, + RPM { + @Override + public String toString() { + return convert("0C"); + } + }, + IAP { + @Override + public String toString() { + return convert("0B"); + } + }, + IAT { + @Override + public String toString() { + return convert("0F"); + } + }, + SHORT_TERM_FUEL_TRIM { + @Override + public String toString() { + return convert("06"); + } + }, + LONG_TERM_FUEL_TRIM { + @Override + public String toString() { + return convert("07"); + } + }, + O2_LAMBDA_PROBE_1_VOLTAGE { + @Override + public String toString() { + return convert("24"); + } + }; + + protected String convert(String string) { + return Integer.toString(incrementBy13(hexToInt(string))); + } + + protected int hexToInt(String string) { + return Integer.valueOf(string, 16); + } + + protected int incrementBy13(int hexToInt) { + return hexToInt + 13; + } + + protected String intToHex(int val) { + String result = Integer.toString(val, 16); + if (result.length() == 1) result = "0"+result; + return "0x".concat(result); + } + } + + private static final String NAME = "a17"; + public static final char RESPONSE_PREFIX_CHAR = 'B'; + public static final char TOKEN_SEPARATOR_CHAR = '<'; + private byte[] bytes; + + + public LegacyCycleCommand(List pidList) { + bytes = new byte[3+pidList.size()]; + byte[] prefix = "a17".getBytes(); + + for (int i = 0; i < prefix.length; i++) { + bytes[i] = prefix[i]; + } + + int i = 0; + for (PID pid : pidList) { + bytes[prefix.length + i++] = (byte) Integer.valueOf(pid.toString()).intValue(); + } + } + + @Override + public byte[] getOutputBytes() { + return bytes; + } + + @Override + public boolean awaitsResults() { + return false; + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/PIDSupportedQuirk.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/PIDSupportedQuirk.java new file mode 100644 index 000000000..37885ceb5 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/adapter/async/PIDSupportedQuirk.java @@ -0,0 +1,31 @@ +package org.envirocar.obd.adapter.async; + +import org.envirocar.obd.adapter.ResponseQuirkWorkaround; + +/** + * Created by matthes on 17.12.15. + */ +public class PIDSupportedQuirk implements ResponseQuirkWorkaround { + + private static final byte[] PREFIX = "B70".getBytes(); + + @Override + public boolean shouldWaitForNextTokenLine(byte[] byteArray) { + if (byteArray.length > 3) { + for (int i = 0; i < PREFIX.length; i++) { + if (byteArray[i] != PREFIX[i]) { + return false; + } + } + + //it is a PID supported, check the correct length + if (byteArray.length < 14) { + return true; + } + } + + return false; + } + + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/bluetooth/BluetoothSocketWrapper.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/bluetooth/BluetoothSocketWrapper.java index 90dc334be..b31992217 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/bluetooth/BluetoothSocketWrapper.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/bluetooth/BluetoothSocketWrapper.java @@ -20,24 +20,63 @@ import android.bluetooth.BluetoothSocket; +import org.envirocar.core.logging.Logger; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -public interface BluetoothSocketWrapper { +/** + * TODO JavaDoc + */ +public abstract class BluetoothSocketWrapper { + private static final Logger LOG = Logger.getLogger(BluetoothSocketWrapper.class); + + public abstract InputStream getInputStream() throws IOException; + + public abstract OutputStream getOutputStream() throws IOException; + + public abstract String getRemoteDeviceName(); + + public abstract void connect() throws IOException; + + public abstract String getRemoteDeviceAddress(); + + public abstract void close() throws IOException; - InputStream getInputStream() throws IOException; + public abstract BluetoothSocket getUnderlyingSocket(); - OutputStream getOutputStream() throws IOException; + public void shutdown() { + LOG.info("Shutting down bluetooth socket..."); - String getRemoteDeviceName(); + try { + if (getInputStream() != null) { + getInputStream().close(); + } else { + LOG.warn("No socket InputStream found for closing"); + } - void connect() throws IOException; + } catch (Exception e) { + LOG.warn(e.getMessage(), e); + } - String getRemoteDeviceAddress(); + try { + if (getOutputStream() != null) { + getOutputStream().close(); + } else { + LOG.warn("No socket OutputStream found for closing"); + } - void close() throws IOException; + } catch (Exception e) { + LOG.warn(e.getMessage(), e); + } - BluetoothSocket getUnderlyingSocket(); + try { + close(); + } catch (Exception e) { + LOG.warn(e.getMessage(), e); + } + LOG.info("bluetooth socket down!"); + } } diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/bluetooth/NativeBluetoothSocket.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/bluetooth/NativeBluetoothSocket.java index 7cdf69d7d..54a0158c7 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/bluetooth/NativeBluetoothSocket.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/bluetooth/NativeBluetoothSocket.java @@ -26,7 +26,10 @@ import java.io.InputStream; import java.io.OutputStream; -public class NativeBluetoothSocket implements BluetoothSocketWrapper { +/** + * TODO JavaDoc + */ +public class NativeBluetoothSocket extends BluetoothSocketWrapper { private BluetoothSocket socket; diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/CommonCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/CommonCommand.java deleted file mode 100644 index 2828209e1..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/CommonCommand.java +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import java.util.HashSet; -import java.util.Set; - -/** - * Abstract command class that the other commands have to extend. Many things - * are imported from Android OBD Reader project! - * - * @author jakob - * - */ -public abstract class CommonCommand { - - private static Set ignoredChars; - private static final char COMMAND_SEND_END = '\r'; - private static final char COMMAND_RECEIVE_END = '>'; - private static final char COMMAND_RECEIVE_SPACE = ' '; - - static { - ignoredChars = new HashSet(); - ignoredChars.add(COMMAND_RECEIVE_SPACE); - ignoredChars.add(COMMAND_SEND_END); - } - - private byte[] rawData = null; - private String command = null; - private Long commandId; - private CommonCommandState commandState; - private String responseTypeId; - private long resultTime; - - - - /** - * Default constructor to use - * - * @param command - * the command to send. This will be the raw data send to the OBD device - * (if a sub-class does not override {@link #getOutgoingBytes()}). - */ - public CommonCommand(String command) { - this.command = command; - determineResponseByte(); - setCommandState(CommonCommandState.NEW); - } - - private void determineResponseByte() { - if (this.command == null || this.command.isEmpty()) return; - - String[] array = this.command.split(" "); - if (array != null && array.length > 1) { - this.responseTypeId = array[1]; - } - } - - /** - * The state of the command. - */ - public enum CommonCommandState { - NEW, RUNNING, FINISHED, EXECUTION_ERROR, QUEUE_ERROR, SEARCHING, UNMATCHED_RESULT - } - - - public String getResponseTypeID() { - return responseTypeId; - } - - - public boolean responseAlwaysRequired() { - return true; - } - - public abstract void parseRawData(); - - - /** - * @return the OBD command name. - */ - public abstract String getCommandName(); - - /** - * @return the commandId - */ - public Long getCommandId() { - return commandId; - } - - - /** - * @return the commandState - */ - public CommonCommandState getCommandState() { - return commandState; - } - - /** - * @param commandState - * the commandState to set - */ - public void setCommandState(CommonCommandState commandState) { - this.commandState = commandState; - } - - @Override - public String toString() { - StringBuffer sb = new StringBuffer(); - sb.append("Commandname: "); - sb.append(getCommandName()); - sb.append(", Command: "); - sb.append(command); - sb.append(", Result Time: "); - sb.append(getResultTime()); - return sb.toString(); - } - - - public void setResultTime(long currentTimeMillis) { - this.resultTime = currentTimeMillis; - } - - public long getResultTime() { - return resultTime; - } - - public byte[] getOutgoingBytes() { - return command.getBytes(); - } - - public void setRawData(byte[] rawData) { - this.rawData = rawData; - } - - public char getEndOfLineReceive() { - return COMMAND_RECEIVE_END; - } - - public char getIgnoreCharReceive() { - return COMMAND_RECEIVE_SPACE; - } - - public char getEndOfLineSend() { - return COMMAND_SEND_END; - } - - public boolean awaitsResults() { - return true; - } - - public byte[] getRawData() { - return this.rawData; - } - - public Set getIgnoredChars() { - return ignoredChars; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Defaults.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Defaults.java deleted file mode 100644 index 962d52c10..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Defaults.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - - -/** - * Turns off line-feed. - */ -public class Defaults extends StringResultCommand { - - public Defaults() { - super("AT D"); - } - - - @Override - public String getCommandName() { - return "Defaults"; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EchoOff.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EchoOff.java deleted file mode 100644 index 5072ad5ca..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EchoOff.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - - -/** - * This command will turn-off echo. - */ -public class EchoOff extends StringResultCommand { - - public EchoOff() { - super("AT E0"); - } - - @Override - public String getCommandName() { - return "Echo Off"; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EnableHeaders.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EnableHeaders.java deleted file mode 100644 index c1541f8de..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EnableHeaders.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - - -/** - * Turns off line-feed. - */ -public class EnableHeaders extends StringResultCommand { - - /** - * @param command - */ - public EnableHeaders() { - super("AT H1"); - } - - - @Override - public String getCommandName() { - return "Enable Headers"; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EngineLoad.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EngineLoad.java deleted file mode 100644 index 9d28755eb..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/EngineLoad.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; - -/** - * EngineLoad Value on PID 01 04 - * - * @author jakob - * - */ -public class EngineLoad extends NumberResultCommand { - - private float value = Float.NaN; - - /** - * Create the Command - */ - public EngineLoad() { - super("01 ".concat(PID.CALCULATED_ENGINE_LOAD.toString())); - } - - @Override - public String getCommandName() { - return "Engine Load"; - } - - - @Override - public Number getNumberResult() { - if (Float.isNaN(value)) { - int[] buffer = getBuffer(); - value = (buffer[2] * 100.0f) / 255.0f; - } - return value; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/FuelPressure.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/FuelPressure.java deleted file mode 100644 index 85907cbf5..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/FuelPressure.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; - -public class FuelPressure extends NumberResultCommand { - - public static final String NAME = "Fuel Pressure"; - private int pressure = Short.MIN_VALUE; - - public FuelPressure() { - super("01 ".concat(PID.FUEL_PRESSURE.toString())); - } - - @Override - public Number getNumberResult() { - if (pressure == Short.MIN_VALUE) { - pressure = getBuffer()[2] * 3; - } - return pressure; - } - - @Override - public String getCommandName() { - return NAME; - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/HeadersOff.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/HeadersOff.java deleted file mode 100644 index 68d6f528e..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/HeadersOff.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - - -/** - * Turns off line-feed. - */ -public class HeadersOff extends StringResultCommand { - - public HeadersOff() { - super("AT H0"); - } - - - @Override - public String getCommandName() { - return "Disable Headers"; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/IntakePressure.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/IntakePressure.java deleted file mode 100644 index 78100cf7f..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/IntakePressure.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; - -/** - * Intake Manifold Pressure on PID 01 0B - * - * @author jakob - * - */ - -public class IntakePressure extends NumberResultCommand { - - public static final String NAME = "Intake Manifold Pressure"; - private int pressure = Short.MIN_VALUE; - - public IntakePressure() { - super("01 ".concat(PID.INTAKE_MAP.toString())); - } - - @Override - public String getCommandName() { - return NAME; - } - - - @Override - public Number getNumberResult() { - if (pressure == Short.MIN_VALUE) { - int[] buffer = getBuffer(); - pressure = buffer[2]; - } - return pressure; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/IntakeTemperature.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/IntakeTemperature.java deleted file mode 100644 index 21072e2ad..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/IntakeTemperature.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; - -/** - * Intake temperature on PID 01 0F - * - * @author jakob - * - */ -public class IntakeTemperature extends NumberResultCommand { - - public static final String NAME = "Air Intake Temperature"; - private int temperature = Short.MIN_VALUE; - - public IntakeTemperature() { - super("01 ".concat(PID.INTAKE_AIR_TEMP.toString())); - } - - @Override - public String getCommandName() { - return NAME; - } - - @Override - public Number getNumberResult() { - if (temperature == Short.MIN_VALUE) { - int[] buffer = getBuffer(); - temperature = buffer[2] - 40; - } - return temperature; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/LineFeedOff.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/LineFeedOff.java deleted file mode 100644 index 706c70f20..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/LineFeedOff.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - - -/** - * Turns off line-feed. - */ -public class LineFeedOff extends StringResultCommand { - - /** - * @param command - */ - public LineFeedOff() { - super("AT L0"); - } - - @Override - public String getCommandName() { - return "Line Feed Off"; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/LongTermTrimBank1.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/LongTermTrimBank1.java deleted file mode 100644 index 71ca43150..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/LongTermTrimBank1.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -/** - * Long Term Fuel Trim (Cylinder) Bank 1 (PID 01 07) - * - * @author jakob - * - */ -public class LongTermTrimBank1 extends NumberResultCommand { - - private double perc = Double.NaN; - - public LongTermTrimBank1() { - super("01 07"); - } - - @Override - public String getCommandName() { - - return "Long Term Fuel Trim Bank 1"; - } - - @Override - public Number getNumberResult() { - if (Double.isNaN(perc)) { - int[] buffer = getBuffer(); - int tmpValue = buffer[2]; - perc = (tmpValue - 128) * (100d / 128d); - } - return perc; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/MAF.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/MAF.java deleted file mode 100644 index 06350c344..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/MAF.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; -import org.envirocar.core.logging.Logger; - -/** - * Mass Air Flow Value PID 01 10 - * - * @author jakob - * - */ -public class MAF extends NumberResultCommand { - - private static final Logger logger = Logger.getLogger(MAF.class); - public static final String NAME = "Mass Air Flow"; - private float maf = Float.NaN; - - public MAF() { - super("01 ".concat(PID.MAF.toString())); - } - - - @Override - public String getCommandName() { - return NAME; - } - - @Override - public Number getNumberResult() { - if (Float.isNaN(maf)) { - int[] buffer = getBuffer(); - try { - if (getCommandState() != CommonCommandState.EXECUTION_ERROR) { - int bytethree = buffer[2]; - int bytefour = buffer[3]; - maf = (bytethree * 256 + bytefour) / 100.0f; - } - } catch (IndexOutOfBoundsException ioobe){ - logger.warn("Get wrong result of the obd adapter"); - } catch (Exception e) { - logger.warn("Error while creating the mass air flow value", e); - } - - } - return maf; - } -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/NumberResultCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/NumberResultCommand.java deleted file mode 100644 index 7cbb4b769..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/NumberResultCommand.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -public abstract class NumberResultCommand extends CommonCommand { - - private static final CharSequence SEARCHING = "SEARCHING"; - private static final CharSequence STOPPED = "STOPPED"; - private static final CharSequence NODATA = "NODATA"; - - static final String STATUS_OK = "41"; - - private int[] buffr; - - /** - * @param command the command to send. This will be the raw data send to the OBD device - * (if a sub-class does not override {@link #getOutgoingBytes()}). - */ - public NumberResultCommand(String command) { - super(command); - } - - @Override - public void parseRawData() { - - int index = 0; - int length = 2; - byte[] data = getRawData(); - - String dataString = new String(data); - - if (isSearching(dataString)) { - setCommandState(CommonCommandState.SEARCHING); - return; - } - else if (isNoDataCommand(dataString)) { - setCommandState(CommonCommandState.EXECUTION_ERROR); - return; - } - - buffr = new int[data.length / 2]; - while (index + length <= data.length) { - String tmp = new String(data, index, length); - - if (index == 0) { - // this is the status - if (!tmp.equals(STATUS_OK)) { - setCommandState(CommonCommandState.EXECUTION_ERROR); - return; - } - } - else if (index == 2) { - // this is the ID byte - if (!tmp.equals(this.getResponseTypeID())) { - setCommandState(CommonCommandState.UNMATCHED_RESULT); - return; - } - } - - /* - * this is a hex number - */ - buffr[index/2] = Integer.parseInt(tmp, 16); - if (buffr[index/2] < 0){ - setCommandState(CommonCommandState.EXECUTION_ERROR); - return; - } - index += length; - } - - setCommandState(CommonCommandState.FINISHED); - } - - public abstract Number getNumberResult(); - - public int[] getBuffer() { - return buffr; - } - - private boolean isSearching(String dataString) { - return dataString.contains(SEARCHING) || dataString.contains(STOPPED); - } - - private boolean isNoDataCommand(String dataString) { - return dataString == null || dataString.contains(NODATA); - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbe.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbe.java deleted file mode 100644 index b8e80f1c8..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbe.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; -import org.envirocar.core.logging.Logger; - -public abstract class O2LambdaProbe extends NumberResultCommand { - - private static final Logger logger = Logger.getLogger(O2LambdaProbe.class); - private String cylinderPosition; - private double equivalenceRation = Double.NaN; - private String pid; - - public static O2LambdaProbe fromPIDEnum(PID pid) { - switch (pid) { - case O2_LAMBDA_PROBE_1_VOLTAGE: - return new O2LambdaProbeVoltage(pid.toString()); - case O2_LAMBDA_PROBE_2_VOLTAGE: - return new O2LambdaProbeVoltage(pid.toString()); - case O2_LAMBDA_PROBE_3_VOLTAGE: - return new O2LambdaProbeVoltage(pid.toString()); - case O2_LAMBDA_PROBE_4_VOLTAGE: - return new O2LambdaProbeVoltage(pid.toString()); - case O2_LAMBDA_PROBE_5_VOLTAGE: - return new O2LambdaProbeVoltage(pid.toString()); - case O2_LAMBDA_PROBE_6_VOLTAGE: - return new O2LambdaProbeVoltage(pid.toString()); - case O2_LAMBDA_PROBE_7_VOLTAGE: - return new O2LambdaProbeVoltage(pid.toString()); - case O2_LAMBDA_PROBE_8_VOLTAGE: - return new O2LambdaProbeVoltage(pid.toString()); - case O2_LAMBDA_PROBE_1_CURRENT: - return new O2LambdaProbeCurrent(pid.toString()); - case O2_LAMBDA_PROBE_2_CURRENT: - return new O2LambdaProbeCurrent(pid.toString()); - case O2_LAMBDA_PROBE_3_CURRENT: - return new O2LambdaProbeCurrent(pid.toString()); - case O2_LAMBDA_PROBE_4_CURRENT: - return new O2LambdaProbeCurrent(pid.toString()); - case O2_LAMBDA_PROBE_5_CURRENT: - return new O2LambdaProbeCurrent(pid.toString()); - case O2_LAMBDA_PROBE_6_CURRENT: - return new O2LambdaProbeCurrent(pid.toString()); - case O2_LAMBDA_PROBE_7_CURRENT: - return new O2LambdaProbeCurrent(pid.toString()); - case O2_LAMBDA_PROBE_8_CURRENT: - return new O2LambdaProbeCurrent(pid.toString()); - - default: - break; - } - - return null; - } - - public O2LambdaProbe(String cylinderPosition) { - super("01 ".concat(cylinderPosition)); - this.cylinderPosition = cylinderPosition; - this.pid = cylinderPosition; - } - - @Override - public Number getNumberResult() { - //command provides two results - return null; - } - - @Override - public void parseRawData() { - super.parseRawData(); - if (getBuffer() == null || getBuffer().length < 6) { - setCommandState(CommonCommandState.EXECUTION_ERROR); - // TODO -// logger.warn("The response did not contain the correct expected count: "+ -// (getBuffer() == null ? "null" : getBuffer().length)); - } - } - - public double getEquivalenceRatio() { - if (Double.isNaN(this.equivalenceRation)) { - int[] data = getBuffer(); - - this.equivalenceRation = ((data[2]*256d)+data[3])/32768d; - } - - return this.equivalenceRation; - } - - - @Override - public String getCommandName() { - return "O2 Lambda Probe "+cylinderPosition; - } - - public String getPID() { - return pid; - } - - public String lambdaString() { - return getClass().getSimpleName() +" ("+pid+"): "+getEquivalenceRatio() +"; "+getNumberResult(); - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbeCurrent.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbeCurrent.java deleted file mode 100644 index 5c941bb99..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbeCurrent.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -public class O2LambdaProbeCurrent extends O2LambdaProbe { - - private double current = Double.NaN; - - public O2LambdaProbeCurrent(String cylinderPosition) { - super(cylinderPosition); - } - - public double getCurrent() { - if (Double.isNaN(current)) { - int[] data = getBuffer(); - - this.current = ((data[4]*256d)+data[5])/256d - 128; - } - - return current; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName()); - sb.append(" Current: "); - sb.append(getCurrent()); - sb.append("; Equivalence Ratio: "); - sb.append(getEquivalenceRatio()); - return sb.toString(); - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbeVoltage.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbeVoltage.java deleted file mode 100644 index 12189a2e0..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/O2LambdaProbeVoltage.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -public class O2LambdaProbeVoltage extends O2LambdaProbe { - - private double voltage = Double.NaN; - - public O2LambdaProbeVoltage(String cylinderPos) { - super(cylinderPos); - } - - public double getVoltage() { - if (Double.isNaN(this.voltage)) { - int[] data = getBuffer(); - - this.voltage = ((data[4]*256d)+data[5])/8192d; - } - - return voltage; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName()); - sb.append(" Voltage: "); - sb.append(getVoltage()); - sb.append("; Equivalence Ratio: "); - sb.append(getEquivalenceRatio()); - return sb.toString(); - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PID.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PID.java new file mode 100644 index 000000000..67e961fb4 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PID.java @@ -0,0 +1,167 @@ +package org.envirocar.obd.commands; + +public enum PID implements PIDEnumInstance { + + FUEL_SYSTEM_STATUS { + @Override + public String getHexadecimalRepresentation() { + return "03"; + } + }, + CALCULATED_ENGINE_LOAD { + @Override + public String getHexadecimalRepresentation() { + return "04"; + } + }, + SHORT_TERM_FUEL_TRIM_BANK_1 { + @Override + public String getHexadecimalRepresentation() { + return "06"; + } + }, + LONG_TERM_FUEL_TRIM_BANK_1 { + @Override + public String getHexadecimalRepresentation() { + return "07"; + } + }, + FUEL_PRESSURE { + @Override + public String getHexadecimalRepresentation() { + return "0A"; + } + }, + INTAKE_MAP { + @Override + public String getHexadecimalRepresentation() { + return "0B"; + } + }, + RPM { + @Override + public String getHexadecimalRepresentation() { + return "0C"; + } + }, + SPEED { + @Override + public String getHexadecimalRepresentation() { + return "0D"; + } + }, + INTAKE_AIR_TEMP { + @Override + public String getHexadecimalRepresentation() { + return "0F"; + } + }, + MAF { + @Override + public String getHexadecimalRepresentation() { + return "10"; + } + }, + TPS { + @Override + public String getHexadecimalRepresentation() { + return "11"; + } + }, + O2_LAMBDA_PROBE_1_VOLTAGE { + @Override + public String getHexadecimalRepresentation() { + return "24"; + } + }, + O2_LAMBDA_PROBE_2_VOLTAGE { + @Override + public String getHexadecimalRepresentation() { + return "25"; + } + }, + O2_LAMBDA_PROBE_3_VOLTAGE { + @Override + public String getHexadecimalRepresentation() { + return "26"; + } + }, + O2_LAMBDA_PROBE_4_VOLTAGE { + @Override + public String getHexadecimalRepresentation() { + return "27"; + } + }, + O2_LAMBDA_PROBE_5_VOLTAGE { + @Override + public String getHexadecimalRepresentation() { + return "28"; + } + }, + O2_LAMBDA_PROBE_6_VOLTAGE { + @Override + public String getHexadecimalRepresentation() { + return "29"; + } + }, + O2_LAMBDA_PROBE_7_VOLTAGE { + @Override + public String getHexadecimalRepresentation() { + return "2A"; + } + }, + O2_LAMBDA_PROBE_8_VOLTAGE { + @Override + public String getHexadecimalRepresentation() { + return "2B"; + } + }, O2_LAMBDA_PROBE_1_CURRENT { + @Override + public String getHexadecimalRepresentation() { + return "34"; + }; + } + , O2_LAMBDA_PROBE_2_CURRENT { + @Override + public String getHexadecimalRepresentation() { + return "35"; + }; + } + , O2_LAMBDA_PROBE_3_CURRENT { + @Override + public String getHexadecimalRepresentation() { + return "36"; + }; + } + , O2_LAMBDA_PROBE_4_CURRENT { + @Override + public String getHexadecimalRepresentation() { + return "37"; + }; + } + , O2_LAMBDA_PROBE_5_CURRENT { + @Override + public String getHexadecimalRepresentation() { + return "38"; + }; + } + , O2_LAMBDA_PROBE_6_CURRENT { + @Override + public String getHexadecimalRepresentation() { + return "39"; + }; + } + , O2_LAMBDA_PROBE_7_CURRENT { + @Override + public String getHexadecimalRepresentation() { + return "3A"; + }; + } + , O2_LAMBDA_PROBE_8_CURRENT { + @Override + public String getHexadecimalRepresentation() { + return "3B"; + }; + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDEnumInstance.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDEnumInstance.java new file mode 100644 index 000000000..5b10ee07e --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDEnumInstance.java @@ -0,0 +1,7 @@ +package org.envirocar.obd.commands; + +public interface PIDEnumInstance { + + String getHexadecimalRepresentation(); + +} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDSupported.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDSupported.java index 57ca1d671..696e9132d 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDSupported.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDSupported.java @@ -18,138 +18,142 @@ */ package org.envirocar.obd.commands; +import org.envirocar.obd.commands.request.BasicCommand; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.UnmatchedResponseException; + +import java.math.BigInteger; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; -import org.envirocar.obd.commands.PIDUtil.PID; - - -/** - * Turns off line-feed. - */ -public class PIDSupported extends CommonCommand { - - private Set pids; - private byte[] bytes; - private String group; - - public PIDSupported() { - this("00"); - } - - /** - * @param group the group of commands ("00", "20", "40" ...) - */ - public PIDSupported(String group) { - super("01 ".concat(group)); - this.group = group; - } - - @Override - public String getCommandName() { - return "PID Supported; Group ".concat(group); - } - - - /** - * @return the set of PIDs that are supported by a car, - * encoded as their HEX byte strings - */ - public Set getSupportedPIDs() { - if (pids == null) { - pids = new HashSet(); - - for (int i = 0; i < bytes.length; i++) { - int current = bytes[i]; - - for (int bit = 3; bit >= 0; bit--) { - boolean is = ((current >> bit) & 1 ) == 1; - if (is) { - /* - * we are starting at PID 01 and not 00 - */ - PID pid = PIDUtil.fromString(createHex(i*4 + (3-bit) + 1)); - if (pid != null) { - pids.add(pid); - } - } - } - - } - } - - return pids; - } - - - private String createHex(int i) { - String result = Integer.toString(i, 16); - if (result.length() == 1) result = "0".concat(result); - return result; - } - - - @Override - public void parseRawData() { - int index = 0; - int length = 2; - - preprocessRawData(); - - byte[] data = getRawData(); - - bytes = new byte[data.length-4]; - - if (bytes.length != 8) { - setCommandState(CommonCommandState.EXECUTION_ERROR); - return; - } - - while (index < data.length) { - if (index == 0) { - String tmp = new String(data, index, length); - // this is the status - if (!tmp.equals(NumberResultCommand.STATUS_OK)) { - setCommandState(CommonCommandState.EXECUTION_ERROR); - return; - } - index += length; - continue; - } - else if (index == 2) { - String tmp = new String(data, index, length); - // this is the ID byte - if (!tmp.equals(this.getResponseTypeID())) { - setCommandState(CommonCommandState.UNMATCHED_RESULT); - return; - } - index += length; - continue; - } - - /* - * this is a hex number - */ - bytes[index-4] = (byte) Integer.valueOf(String.valueOf((char) data[index]), 16).intValue(); - if (bytes[index-4] < 0){ - setCommandState(CommonCommandState.EXECUTION_ERROR); - return; - } - index++; - } - - setCommandState(CommonCommandState.FINISHED); - } - - - private void preprocessRawData() { - byte[] data = getRawData(); - String str = new String(data); - if (str.contains("4100")) { - int index = str.indexOf("4100"); - setRawData(Arrays.copyOfRange(data, index, data.length)); - } - } +public class PIDSupported implements BasicCommand { + + private final byte[] output; + private Set pids; + private byte[] bytes; + private String group; + + public PIDSupported() { + this("00"); + } + + /** + * @param group the group of commands ("00", "20", "40" ...) + */ + public PIDSupported(String group) { + this.output = "01 ".concat(group).getBytes(); + this.group = group; + } + + private String createHex(int i) { + String result = Integer.toString(i, 16); + if (result.length() == 1) result = "0".concat(result); + return result; + } + + + public Set parsePIDs(byte[] rawData) throws InvalidCommandResponseException, NoDataReceivedException, UnmatchedResponseException, AdapterSearchingException { + if (rawData == null) { + throw new InvalidCommandResponseException("Null response on PIDSupported request"); + } + + String rawAsString = new String(rawData); + int startIndex = rawAsString.indexOf("41".concat(group)); + if (startIndex >= 0) { + if (rawData.length < startIndex + 12) { + throw new InvalidCommandResponseException("The response was too small"); + } + + String receivedGroup = rawAsString.substring(startIndex + 2, startIndex + 4); + if (!receivedGroup.equals(group)) { + throw new InvalidCommandResponseException("Unexpected group received: "+receivedGroup); + } + rawData = rawAsString.substring(startIndex+4, startIndex + 12).getBytes(); + } + else { + throw new InvalidCommandResponseException("The expected status response '41"+ group +"' was not in the response"); + } + + if (rawData.length != 8) { + throw new InvalidCommandResponseException("Invalid PIDSupported length: "+rawData.length); + } + + try { + List pids = new ArrayList<>(8); + + int groupOffset = Integer.parseInt(this.group, 16); + char[] binaries; + byte b; + + /** + * the 32 PIDs of the group are encoded bitwise from MSB to LSB (resulting in 8 bytes): + * assuming group 00 and the first byte is a 0x0A = (int) 10, + * then bits 4 (MSB) and 2 are set, resulting in PIDs 0x01 and 0x03 (counting starts at + * PID 0x01, 0x21, ...) are supported + */ + for (int i = 0; i < rawData.length; i++) { + b = rawData[i]; + int fromHex = Integer.parseInt(new String(new char[] {'0', (char) b}), 16); + BigInteger bigInt = BigInteger.valueOf(fromHex); + for (int j = 3; j >= 0; j--) { + //check from MSB down to LSB + if (bigInt.testBit(j)) { + //create an int representation and apply the group offset + pids.add(1 + (i*4 + 3-j) + groupOffset); + } + } + } + + Set list = new HashSet<>(); + /** + * conver to hex string so the PIDUtil can parse it + */ + for (Integer pidInt : pids) { + String hex = Integer.toHexString(pidInt); + if (hex.length() == 1) { + hex = "0".concat(hex); + } + PID tmp = PIDUtil.fromString(hex); + if (tmp != null) { + list.add(tmp); + } + } + + return list; + } + catch (RuntimeException e) { + throw new InvalidCommandResponseException("The response contained invalid byte values: "+e.getMessage()); + } + + } + + + private byte[] preProcessRawData(byte[] data) { + String str = new String(data); + if (str.contains("41".concat(this.group))) { + int index = str.indexOf("41".concat(this.group)); + return Arrays.copyOfRange(data, index, data.length); + } + return data; + } + + @Override + public byte[] getOutputBytes() { + return output; + } + + @Override + public boolean awaitsResults() { + return true; + } + + public String getGroup() { + return group; + } } \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDUtil.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDUtil.java index 4d53a27c2..4348c90a7 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDUtil.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/PIDUtil.java @@ -18,158 +18,19 @@ */ package org.envirocar.obd.commands; -public class PIDUtil { - - public enum PID { +import org.envirocar.core.entity.Measurement; +import org.envirocar.obd.commands.request.ModeOneCommand; +import org.envirocar.obd.commands.request.PIDCommand; - FUEL_SYSTEM_STATUS { - @Override - public String toString() { - return "03"; - } - }, - CALCULATED_ENGINE_LOAD { - @Override - public String toString() { - return "04"; - } - }, - FUEL_PRESSURE { - @Override - public String toString() { - return "0A"; - } - }, - INTAKE_MAP { - @Override - public String toString() { - return "0B"; - } - }, - RPM { - @Override - public String toString() { - return "0C"; - } - }, - SPEED { - @Override - public String toString() { - return "0D"; - } - }, - INTAKE_AIR_TEMP { - @Override - public String toString() { - return "0F"; - } - }, - MAF { - @Override - public String toString() { - return "10"; - } - }, - TPS { - @Override - public String toString() { - return "11"; - } - }, - O2_LAMBDA_PROBE_1_VOLTAGE { - @Override - public String toString() { - return "24"; - } - }, - O2_LAMBDA_PROBE_2_VOLTAGE { - @Override - public String toString() { - return "25"; - } - }, - O2_LAMBDA_PROBE_3_VOLTAGE { - @Override - public String toString() { - return "26"; - } - }, - O2_LAMBDA_PROBE_4_VOLTAGE { - @Override - public String toString() { - return "27"; - } - }, - O2_LAMBDA_PROBE_5_VOLTAGE { - @Override - public String toString() { - return "28"; - } - }, - O2_LAMBDA_PROBE_6_VOLTAGE { - @Override - public String toString() { - return "29"; - } - }, - O2_LAMBDA_PROBE_7_VOLTAGE { - @Override - public String toString() { - return "2A"; - } - }, - O2_LAMBDA_PROBE_8_VOLTAGE { - @Override - public String toString() { - return "2B"; - } - }, O2_LAMBDA_PROBE_1_CURRENT { - public String toString() { - return "34"; - }; - } - , O2_LAMBDA_PROBE_2_CURRENT { - public String toString() { - return "35"; - }; - } - , O2_LAMBDA_PROBE_3_CURRENT { - public String toString() { - return "36"; - }; - } - , O2_LAMBDA_PROBE_4_CURRENT { - public String toString() { - return "37"; - }; - } - , O2_LAMBDA_PROBE_5_CURRENT { - public String toString() { - return "38"; - }; - } - , O2_LAMBDA_PROBE_6_CURRENT { - public String toString() { - return "39"; - }; - } - , O2_LAMBDA_PROBE_7_CURRENT { - public String toString() { - return "3A"; - }; - } - , O2_LAMBDA_PROBE_8_CURRENT { - public String toString() { - return "3B"; - }; - } - } +public class PIDUtil { public static PID fromString(String s) { - if (s == null || s.isEmpty()) return null; + if (s == null || s.isEmpty()) { + return null; + } for (PID p : PID.values()) { - if (s.equalsIgnoreCase(p.toString())) { + if (s.equalsIgnoreCase(p.getHexadecimalRepresentation())) { return p; } } @@ -178,48 +39,79 @@ public static PID fromString(String s) { } - public static CommonCommand instantiateCommand(String pid) { + public static PIDCommand instantiateCommand(String pid) { return instantiateCommand(fromString(pid)); } - public static CommonCommand instantiateCommand(PID pid) { + public static PIDCommand instantiateCommand(PID pid) { switch (pid) { - case FUEL_SYSTEM_STATUS: - return new FuelSystemStatus(); - case CALCULATED_ENGINE_LOAD: - return new EngineLoad(); - case FUEL_PRESSURE: - return new FuelPressure(); - case INTAKE_MAP: - return new IntakePressure(); - case RPM: - return new RPM(); - case SPEED: - return new Speed(); - case INTAKE_AIR_TEMP: - return new IntakeTemperature(); - case MAF: - return new MAF(); - case TPS: - return new TPS(); - case O2_LAMBDA_PROBE_1_VOLTAGE: - return O2LambdaProbe.fromPIDEnum(pid); - case O2_LAMBDA_PROBE_2_VOLTAGE: - return O2LambdaProbe.fromPIDEnum(pid); - case O2_LAMBDA_PROBE_3_VOLTAGE: - return O2LambdaProbe.fromPIDEnum(pid); - case O2_LAMBDA_PROBE_4_VOLTAGE: - return O2LambdaProbe.fromPIDEnum(pid); - case O2_LAMBDA_PROBE_5_VOLTAGE: - return O2LambdaProbe.fromPIDEnum(pid); - case O2_LAMBDA_PROBE_6_VOLTAGE: - return O2LambdaProbe.fromPIDEnum(pid); - case O2_LAMBDA_PROBE_7_VOLTAGE: - return O2LambdaProbe.fromPIDEnum(pid); - case O2_LAMBDA_PROBE_8_VOLTAGE: - return O2LambdaProbe.fromPIDEnum(pid); - default: - return null; + case FUEL_SYSTEM_STATUS: + case CALCULATED_ENGINE_LOAD: + case FUEL_PRESSURE: + case INTAKE_MAP: + case RPM: + case SPEED: + case INTAKE_AIR_TEMP: + case MAF: + case TPS: + case O2_LAMBDA_PROBE_1_VOLTAGE: + case O2_LAMBDA_PROBE_2_VOLTAGE: + case O2_LAMBDA_PROBE_3_VOLTAGE: + case O2_LAMBDA_PROBE_4_VOLTAGE: + case O2_LAMBDA_PROBE_5_VOLTAGE: + case O2_LAMBDA_PROBE_6_VOLTAGE: + case O2_LAMBDA_PROBE_7_VOLTAGE: + case O2_LAMBDA_PROBE_8_VOLTAGE: + return new ModeOneCommand(pid); + default: + return null; } } + + public static Measurement.PropertyKey toPropertyKey(PID pid) { + switch (pid) { + + case FUEL_SYSTEM_STATUS: + return Measurement.PropertyKey.FUEL_SYSTEM_STATUS_CODE; + case CALCULATED_ENGINE_LOAD: + return Measurement.PropertyKey.ENGINE_LOAD; + case SHORT_TERM_FUEL_TRIM_BANK_1: + return Measurement.PropertyKey.SHORT_TERM_TRIM_1; + case LONG_TERM_FUEL_TRIM_BANK_1: + return Measurement.PropertyKey.LONG_TERM_TRIM_1; + case FUEL_PRESSURE: + return null; + case INTAKE_MAP: + return Measurement.PropertyKey.INTAKE_PRESSURE; + case RPM: + return Measurement.PropertyKey.RPM; + case SPEED: + return Measurement.PropertyKey.SPEED; + case INTAKE_AIR_TEMP: + return Measurement.PropertyKey.INTAKE_TEMPERATURE; + case MAF: + return Measurement.PropertyKey.MAF; + case TPS: + return Measurement.PropertyKey.THROTTLE_POSITON; + case O2_LAMBDA_PROBE_1_VOLTAGE: + case O2_LAMBDA_PROBE_2_VOLTAGE: + case O2_LAMBDA_PROBE_3_VOLTAGE: + case O2_LAMBDA_PROBE_4_VOLTAGE: + case O2_LAMBDA_PROBE_5_VOLTAGE: + case O2_LAMBDA_PROBE_6_VOLTAGE: + case O2_LAMBDA_PROBE_7_VOLTAGE: + case O2_LAMBDA_PROBE_8_VOLTAGE: + return Measurement.PropertyKey.LAMBDA_VOLTAGE_ER; + case O2_LAMBDA_PROBE_1_CURRENT: + case O2_LAMBDA_PROBE_2_CURRENT: + case O2_LAMBDA_PROBE_3_CURRENT: + case O2_LAMBDA_PROBE_4_CURRENT: + case O2_LAMBDA_PROBE_5_CURRENT: + case O2_LAMBDA_PROBE_6_CURRENT: + case O2_LAMBDA_PROBE_7_CURRENT: + case O2_LAMBDA_PROBE_8_CURRENT: + return Measurement.PropertyKey.LAMBDA_CURRENT_ER; + } + return null; + } } diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/RPM.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/RPM.java deleted file mode 100644 index f83f8f617..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/RPM.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; - -/** - * Engine RPM on PID 01 0C - * - * @author jakob - * - */ -public class RPM extends NumberResultCommand { - - public static final String NAME = "Engine RPM"; - private int rpm = Short.MIN_VALUE; - - public RPM() { - super("01 ".concat(PID.RPM.toString())); - } - - - @Override - public String getCommandName() { - return NAME; - } - - @Override - public Number getNumberResult() { - if (rpm == Short.MIN_VALUE) { - int[] buffer = getBuffer(); - int bytethree = buffer[2]; - int bytefour = buffer[3]; - rpm = (bytethree * 256 + bytefour) / 4; - } - return rpm; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/SelectAutoProtocol.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/SelectAutoProtocol.java deleted file mode 100644 index 66874fd64..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/SelectAutoProtocol.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - - -/** - * Select the protocol to use. - */ -public class SelectAutoProtocol extends StringResultCommand { - - public SelectAutoProtocol() { - super("AT SP " + 0); - } - - - @Override - public String getCommandName() { - return "Protocol: Auto"; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/ShortTermTrimBank1.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/ShortTermTrimBank1.java deleted file mode 100644 index e1872e566..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/ShortTermTrimBank1.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -/** - * Short Term Trim (Cylinder) Bank 1, PID 01 06 - * - * @author jakob - * - */ -public class ShortTermTrimBank1 extends NumberResultCommand { - - private double fuelTrimValue = Double.NaN; - - public ShortTermTrimBank1() { - super("01 06"); - } - - - @Override - public String getCommandName() { - - return "Short Term Fuel Trim Bank 1"; - } - - @Override - public Number getNumberResult() { - if (Double.isNaN(fuelTrimValue)) { - int[] buffer = getBuffer(); - fuelTrimValue = (buffer[2] - 128) * (100d / 128d); - } - return fuelTrimValue; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/SpacesOff.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/SpacesOff.java deleted file mode 100644 index 1bc022166..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/SpacesOff.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - - -/** - * This command will turn-off echo. - */ -public class SpacesOff extends StringResultCommand { - - public SpacesOff() { - super("AT S0"); - } - - @Override - public String getCommandName() { - return "Spaces Off"; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Speed.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Speed.java deleted file mode 100644 index 50459d2e2..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Speed.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; -import org.envirocar.core.logging.Logger; - -/** - * Speed Command PID 01 0D - * - * @author jakob - */ -public class Speed extends NumberResultCommand { - - private static final Logger LOG = Logger.getLogger(Speed.class); - public static final String NAME = "Vehicle Speed"; - private int metricSpeed = Short.MIN_VALUE; - - private static int mLastVal = 0; - - public Speed() { - super("01 ".concat(PID.SPEED.toString())); - } - - @Override - public void parseRawData() { - super.parseRawData(); - - if (getNumberResult() != null) { - int val = getNumberResult().intValue(); - if (val - mLastVal > 49) { - LOG.warn(String.format("Received a speed value of %s. this is probably an " + - "erroneous response", val)); - } - mLastVal = val; - } - } - - @Override - public String getCommandName() { - return NAME; - } - - @Override - public Number getNumberResult() { - int[] buffer = getBuffer(); - if (metricSpeed == Short.MIN_VALUE) { - metricSpeed = buffer[2]; - } - return metricSpeed; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/StringResultCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/StringResultCommand.java deleted file mode 100644 index 0550c7ab4..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/StringResultCommand.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -public abstract class StringResultCommand extends CommonCommand { - - /** - * @param command the command to send. This will be the raw data send to the OBD device - * (if a sub-class does not override {@link #getOutgoingBytes()}). - */ - public StringResultCommand(String command) { - super(command); - } - - @Override - public void parseRawData() { - setCommandState(CommonCommandState.FINISHED); - } - - public String getStringResult() { - return new String(getRawData()); - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/TPS.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/TPS.java deleted file mode 100644 index 85a77c385..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/TPS.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.commands; - -import org.envirocar.obd.commands.PIDUtil.PID; - -/** - * Throttle position on PID 01 11 - * - * @author jakob - * - */ -public class TPS extends NumberResultCommand { - - private int value = Short.MIN_VALUE; - - public TPS() { - super("01 ".concat(PID.TPS.toString())); - } - - @Override - public String getCommandName() { - return "Throttle Position"; - } - - @Override - public Number getNumberResult() { - if (value == Short.MIN_VALUE) { - int[] buffer = getBuffer(); - value = (buffer[2] * 100) / 255; - } - return value; - } - -} \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/BasicCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/BasicCommand.java new file mode 100644 index 000000000..d9cabe0b2 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/BasicCommand.java @@ -0,0 +1,9 @@ +package org.envirocar.obd.commands.request; + +public interface BasicCommand { + + byte[] getOutputBytes(); + + boolean awaitsResults(); + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/ModeOneCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/ModeOneCommand.java new file mode 100644 index 000000000..26db333da --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/ModeOneCommand.java @@ -0,0 +1,14 @@ +package org.envirocar.obd.commands.request; + +import org.envirocar.obd.commands.PID; + +/** + * Created by matthes on 31.10.15. + */ +public class ModeOneCommand extends PIDCommand { + + public ModeOneCommand(PID pid) { + super("01", pid); + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/PIDCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/PIDCommand.java new file mode 100644 index 000000000..c63921132 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/PIDCommand.java @@ -0,0 +1,82 @@ +package org.envirocar.obd.commands.request; + +import org.envirocar.obd.commands.PID; + +public class PIDCommand implements BasicCommand { + + private final int expectedResponseLines; + private String mode; + private PID pid; + private byte[] bytes; + + /** + * @param mode the mode of the PID cmd + * @param pid the PID to be requested + * + */ + public PIDCommand(String mode, PID pid) { + this(mode, pid, 1); + } + + /** + * @param mode the mode of the PID cmd + * @param pid the PID to be requested + * @param expectedResponseLines the number of response lines to be expected after execution. + * The OBD adapter will wait for this amount of lines and write + * the response immediately after reception of it. E.g. providing 1 + * results in a fast response. Providing 0 will result in the + * default OBD behaviour of waiting 200ms for responses and return + * the first. + */ + public PIDCommand(String mode, PID pid, int expectedResponseLines) { + if (expectedResponseLines < 0 || expectedResponseLines > 9) { + throw new IllegalStateException("expectedResponseLines out of allowed bounds"); + } + + this.mode = mode; + this.pid = pid; + this.expectedResponseLines = expectedResponseLines; + + prepareBytes(); + } + + private void prepareBytes() { + int ml = mode.length(); + String pidString = pid.getHexadecimalRepresentation(); + int pl = pidString.length(); + bytes = new byte[ml + pl + 1 + (expectedResponseLines == 0 ? 0 : 1)]; + + int pos = 0; + for (int i = 0; i < ml; i++) { + bytes[pos++] = (byte) mode.charAt(i); + } + + bytes[pos++] = ' '; + + for (int i = 0; i < pl; i++) { + bytes[pos++] = (byte) pidString.charAt(i); + } + + if (expectedResponseLines > 0) { + bytes[pos++] = (byte) Integer.toString(this.expectedResponseLines).charAt(0); + } + } + + public String getMode() { + return mode; + } + + public PID getPid() { + return pid; + } + + @Override + public byte[] getOutputBytes() { + return bytes; + } + + @Override + public boolean awaitsResults() { + return true; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/ConfigurationCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/ConfigurationCommand.java new file mode 100644 index 000000000..3e2f96eb6 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/ConfigurationCommand.java @@ -0,0 +1,69 @@ +package org.envirocar.obd.commands.request.elm; + +import org.envirocar.obd.commands.request.BasicCommand; + +public class ConfigurationCommand implements BasicCommand { + + private final String output; + private final boolean awaitsResult; + private final Instance instance; + + public ConfigurationCommand(String output, Instance i, boolean awaitsResult) { + this.output = output; + this.awaitsResult = awaitsResult; + this.instance = i; + } + + @Override + public byte[] getOutputBytes() { + return this.output.getBytes(); + } + + @Override + public boolean awaitsResults() { + return this.awaitsResult; + } + + public Instance getInstance() { + return instance; + } + + public static ConfigurationCommand instance(Instance i) { + switch (i) { + case DEFAULTS: + return new ConfigurationCommand("AT D", i, true); + case ECHO_OFF: + return new ConfigurationCommand("AT E0", i, true); + case HEADERS_ON: + return new ConfigurationCommand("AT H1", i, true); + case HEADERS_OFF: + return new ConfigurationCommand("AT H0", i, true); + case LINE_FEED_OFF: + return new ConfigurationCommand("AT L0", i, true); + case MEMORY_OFF: + return new ConfigurationCommand("AT M0", i, true); + case RESET: + return new ConfigurationCommand("AT Z", i, false); + case SELECT_AUTO_PROTOCOL: + return new ConfigurationCommand("AT SP 0", i, true); + case SPACES_OFF: + return new ConfigurationCommand("AT S0", i, true); + } + + return null; + } + + public enum Instance { + DEFAULTS, + ECHO_OFF, + HEADERS_ON, + HEADERS_OFF, + LINE_FEED_OFF, + MEMORY_OFF, + RESET, + SELECT_AUTO_PROTOCOL, + TIMEOUT, + SPACES_OFF + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/DelayedConfigurationCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/DelayedConfigurationCommand.java new file mode 100644 index 000000000..35a8bd638 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/DelayedConfigurationCommand.java @@ -0,0 +1,30 @@ +package org.envirocar.obd.commands.request.elm; + +import org.envirocar.core.logging.Logger; + +/** + * Created by matthes on 03.11.15. + */ +public class DelayedConfigurationCommand extends ConfigurationCommand { + + private static final Logger LOG = Logger.getLogger(DelayedConfigurationCommand.class); + + private final long delay; + + public DelayedConfigurationCommand(String output, Instance i, boolean awaitsResult, long delay) { + super(output, i, awaitsResult); + this.delay = delay; + } + + @Override + public byte[] getOutputBytes() { + if (this.delay > 0) { + try { + Thread.sleep(this.delay); + } catch (InterruptedException e) { + LOG.warn(e.getMessage(), e); + } + } + return super.getOutputBytes(); + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Timeout.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/Timeout.java similarity index 84% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Timeout.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/Timeout.java index cea406d98..5ba1138ef 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/Timeout.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/request/elm/Timeout.java @@ -16,14 +16,15 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.commands; + +package org.envirocar.obd.commands.request.elm; /** * This will set the value of time in milliseconds (ms) that the OBD interface * will wait for a response from the ECU. If exceeds, the response is "NO DATA". */ -public class Timeout extends StringResultCommand { +public class Timeout extends ConfigurationCommand { /** * @param timeout @@ -31,13 +32,8 @@ public class Timeout extends StringResultCommand { * desired timeout in milliseconds (ms). */ public Timeout(int timeout) { - super("AT ST " + Integer.toHexString(0xFF & timeout)); + super("AT ST " + Integer.toHexString(0xFF & timeout), Instance.TIMEOUT, true); } - @Override - public String getCommandName() { - return "Timeout"; - } - } \ No newline at end of file diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/CommandResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/CommandResponse.java new file mode 100644 index 000000000..4f2cbd72a --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/CommandResponse.java @@ -0,0 +1,7 @@ +package org.envirocar.obd.commands.response; + +/** + * Created by matthes on 29.10.15. + */ +public class CommandResponse { +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/DataResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/DataResponse.java new file mode 100644 index 000000000..42896864f --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/DataResponse.java @@ -0,0 +1,29 @@ +package org.envirocar.obd.commands.response; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.events.Timestamped; + +public abstract class DataResponse extends CommandResponse implements Timestamped { + + private final long timestamp; + + public DataResponse() { + this.timestamp = System.currentTimeMillis(); + } + + public long getTimestamp() { + return timestamp; + } + + public abstract PID getPid(); + + public abstract Number getValue(); + + public boolean isComposite() { + return false; + } + + public Number[] getCompositeValues() { + return new Number[] {getValue()}; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/MetadataResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/MetadataResponse.java new file mode 100644 index 000000000..a75849df8 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/MetadataResponse.java @@ -0,0 +1,7 @@ +package org.envirocar.obd.commands.response; + +/** + * Created by matthes on 29.10.15. + */ +public class MetadataResponse extends CommandResponse { +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/ResponseParser.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/ResponseParser.java new file mode 100644 index 000000000..c92cadfa5 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/ResponseParser.java @@ -0,0 +1,160 @@ +package org.envirocar.obd.commands.response; + +import org.envirocar.core.logging.Logger; +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.PIDUtil; +import org.envirocar.obd.commands.response.entity.EngineLoadResponse; +import org.envirocar.obd.commands.response.entity.EngineRPMResponse; +import org.envirocar.obd.commands.response.entity.FuelPressureResponse; +import org.envirocar.obd.commands.response.entity.FuelSystemStatusResponse; +import org.envirocar.obd.commands.response.entity.GenericDataResponse; +import org.envirocar.obd.commands.response.entity.IntakeAirTemperatureResponse; +import org.envirocar.obd.commands.response.entity.IntakeManifoldAbsolutePressureResponse; +import org.envirocar.obd.commands.response.entity.LambdaProbeCurrentResponse; +import org.envirocar.obd.commands.response.entity.LambdaProbeVoltageResponse; +import org.envirocar.obd.commands.response.entity.LongTermFuelTrimResponse; +import org.envirocar.obd.commands.response.entity.MAFResponse; +import org.envirocar.obd.commands.response.entity.ShortTermFuelTrimResponse; +import org.envirocar.obd.commands.response.entity.SpeedResponse; +import org.envirocar.obd.commands.response.entity.ThrottlePositionResponse; +import org.envirocar.obd.exception.AdapterSearchingException; +import org.envirocar.obd.exception.InvalidCommandResponseException; +import org.envirocar.obd.exception.NoDataReceivedException; +import org.envirocar.obd.exception.UnmatchedResponseException; + +public class ResponseParser { + + private static final Logger LOGGER = Logger.getLogger(ResponseParser.class); + + private static final CharSequence SEARCHING = "SEARCHING"; + private static final CharSequence STOPPED = "STOPPED"; + private static final CharSequence NO_DATA = "NODATA"; + public static final String STATUS_OK = "41"; + + public ResponseParser() { + + } + + public DataResponse parse(byte[] data) throws AdapterSearchingException, NoDataReceivedException, + InvalidCommandResponseException, UnmatchedResponseException { + + /** + * we received a char array as hexadecimal --> + * two chars represent one byte + */ + int index = 0; + int length = 2; + + String dataString = new String(data); + + //cartrend: 7E803410D00AAAAAAAA + //= 410D00AAAAAAAA +// if (dataString.startsWith("7E803")) { +// dataString = dataString.substring(5, dataString.length()); +// } + + if (isSearching(dataString)) { + throw new AdapterSearchingException(); + } + else if (isNoDataCommand(dataString)) { + throw new NoDataReceivedException("NODATA was received"); + } + + int[] buffer = new int[data.length / 2]; + boolean error = false; + PID pid = null; + while (index + length <= data.length) { + String tmp = new String(data, index, length); + + // this is the status + if (index == 0) { + if (!tmp.equals(STATUS_OK)) { + error = true; + } + } + // this is the ID byte + else if (index == 2) { + pid = PIDUtil.fromString(tmp); + if (error || pid == null) { + throw new InvalidCommandResponseException(pid == null ? tmp : pid.toString()); + } + } + + else { + /* + * this is a hex number + */ + buffer[index/2] = Integer.parseInt(tmp, 16); + if (buffer[index/2] < 0) { + throw new InvalidCommandResponseException(pid.toString()); + } + } + + index += length; + } + + return createDataResponse(pid, buffer, data); + } + + private DataResponse createDataResponse(PID pid, int[] processedData, byte[] rawData) throws InvalidCommandResponseException, UnmatchedResponseException { + switch (pid) { + case FUEL_SYSTEM_STATUS: + return FuelSystemStatusResponse.fromRawData(rawData); + case CALCULATED_ENGINE_LOAD: + return new EngineLoadResponse((processedData[2] * 100.0f) / 255.0f); + case FUEL_PRESSURE: + return new FuelPressureResponse(processedData[2] * 3); + case INTAKE_MAP: + return new IntakeManifoldAbsolutePressureResponse(processedData[2]); + case RPM: + return new EngineRPMResponse((processedData[2] * 256 + processedData[3]) / 4); + case SPEED: + return new SpeedResponse(processedData[2]); + case INTAKE_AIR_TEMP: + return new IntakeAirTemperatureResponse(processedData[2] - 40); + case MAF: + return new MAFResponse((processedData[2] * 256 + processedData[3]) / 100.0f); + case TPS: + return new ThrottlePositionResponse((processedData[2] * 100) / 255); + case SHORT_TERM_FUEL_TRIM_BANK_1: + return new ShortTermFuelTrimResponse((processedData[2] - 128) * (100d / 128d), 1); + case LONG_TERM_FUEL_TRIM_BANK_1: + return new LongTermFuelTrimResponse((processedData[2] - 128) * (100d / 128d), 1); + case O2_LAMBDA_PROBE_1_VOLTAGE: + case O2_LAMBDA_PROBE_2_VOLTAGE: + case O2_LAMBDA_PROBE_3_VOLTAGE: + case O2_LAMBDA_PROBE_4_VOLTAGE: + case O2_LAMBDA_PROBE_5_VOLTAGE: + case O2_LAMBDA_PROBE_6_VOLTAGE: + case O2_LAMBDA_PROBE_7_VOLTAGE: + case O2_LAMBDA_PROBE_8_VOLTAGE: + LambdaProbeVoltageResponse lambda = new LambdaProbeVoltageResponse( + ((processedData[4] * 256d) + processedData[5]) / 8192d, + ((processedData[2] * 256d) + processedData[3]) / 32768d); + return lambda; + case O2_LAMBDA_PROBE_1_CURRENT: + case O2_LAMBDA_PROBE_2_CURRENT: + case O2_LAMBDA_PROBE_3_CURRENT: + case O2_LAMBDA_PROBE_4_CURRENT: + case O2_LAMBDA_PROBE_5_CURRENT: + case O2_LAMBDA_PROBE_6_CURRENT: + case O2_LAMBDA_PROBE_7_CURRENT: + case O2_LAMBDA_PROBE_8_CURRENT: + return new LambdaProbeCurrentResponse( + ((processedData[4]*256d) + processedData[5])/256d - 128, + ((processedData[2]*256d) + processedData[3]) / 32768d); + } + + return new GenericDataResponse(pid, processedData, rawData); + } + + private boolean isSearching(String dataString) { + return dataString.contains(SEARCHING) || dataString.contains(STOPPED); + } + + private boolean isNoDataCommand(String dataString) { + return dataString == null || dataString.contains(NO_DATA); + } + + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/EngineLoadResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/EngineLoadResponse.java new file mode 100644 index 000000000..3dade615c --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/EngineLoadResponse.java @@ -0,0 +1,22 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +public class EngineLoadResponse extends DataResponse { + + private final float value; + + public EngineLoadResponse(float v) { + this.value = v; + } + + public Number getValue() { + return value; + } + + @Override + public PID getPid() { + return PID.CALCULATED_ENGINE_LOAD; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/EngineRPMResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/EngineRPMResponse.java new file mode 100644 index 000000000..9240dd604 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/EngineRPMResponse.java @@ -0,0 +1,24 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class EngineRPMResponse extends DataResponse { + private final int value; + + public EngineRPMResponse(int v) { + this.value = v; + } + + public Number getValue() { + return value; + } + + @Override + public PID getPid() { + return PID.RPM; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/FuelPressureResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/FuelPressureResponse.java new file mode 100644 index 000000000..f6fe0dd81 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/FuelPressureResponse.java @@ -0,0 +1,24 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class FuelPressureResponse extends DataResponse { + private final int value; + + public FuelPressureResponse(int v) { + this.value = v; + } + + public Number getValue() { + return value; + } + + @Override + public PID getPid() { + return PID.FUEL_PRESSURE; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/FuelSystemStatus.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/FuelSystemStatusResponse.java similarity index 65% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/commands/FuelSystemStatus.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/FuelSystemStatusResponse.java index c8791f832..5a9cd1d5c 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/FuelSystemStatus.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/FuelSystemStatusResponse.java @@ -16,61 +16,52 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.commands; +package org.envirocar.obd.commands.response.entity; -import org.envirocar.obd.commands.PIDUtil.PID; +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.exception.UnmatchedResponseException; +import org.envirocar.obd.commands.response.DataResponse; +import org.envirocar.obd.exception.InvalidCommandResponseException; -public class FuelSystemStatus extends CommonCommand { +public class FuelSystemStatusResponse extends DataResponse { - public static final String NAME = "Fuel System Status"; private int setBit; - public FuelSystemStatus() { - super("01 ".concat(PID.FUEL_SYSTEM_STATUS.toString())); - } + public static FuelSystemStatusResponse fromRawData(byte[] data) throws InvalidCommandResponseException, UnmatchedResponseException { + FuelSystemStatusResponse result = new FuelSystemStatusResponse(); - @Override - public void parseRawData() { /* * big try catch as it is not robustly tested */ try { int index = 0; int length = 2; - byte[] data = getRawData(); - + if (data.length != 6 && data.length != 8) { - setCommandState(CommonCommandState.EXECUTION_ERROR); + throw new InvalidCommandResponseException(PID.FUEL_SYSTEM_STATUS.toString()); } while (index < data.length) { String tmp = new String(data, index, length); if (index == 0) { - - // this is the status - if (!tmp.equals(NumberResultCommand.STATUS_OK)) { - setCommandState(CommonCommandState.EXECUTION_ERROR); - return; - } + // we can assume a valid response (called after check) index += length; continue; } else if (index == 2) { // this is the ID byte - if (!tmp.equals(this.getResponseTypeID())) { - setCommandState(CommonCommandState.UNMATCHED_RESULT); - return; + if (!tmp.equals(PID.FUEL_SYSTEM_STATUS)) { + throw new UnmatchedResponseException(); } index += length; continue; } else if (index == 4) { int value = Integer.valueOf(tmp, 16); - setBit = determineSetBit(value); - if (setBit == -1) { - setCommandState(CommonCommandState.EXECUTION_ERROR); - return; + result.setBit = determineSetBit(value); + if (result.setBit == -1) { + throw new InvalidCommandResponseException(PID.FUEL_SYSTEM_STATUS.toString()); } index += length; } @@ -81,11 +72,13 @@ else if (index == 6) { } } catch (RuntimeException e) { - setCommandState(CommonCommandState.EXECUTION_ERROR); + throw new InvalidCommandResponseException(PID.FUEL_SYSTEM_STATUS.toString()); } + + return result; } - private int determineSetBit(int value) { + private static int determineSetBit(int value) { if (value == 0) { return 0; } @@ -127,13 +120,18 @@ public boolean isInClosedLoop() { } } - @Override - public String getCommandName() { - return NAME; - } - + public int getStatus() { return setBit; } + @Override + public PID getPid() { + return PID.FUEL_SYSTEM_STATUS; + } + + @Override + public Number getValue() { + return getStatus(); + } } diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/GenericDataResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/GenericDataResponse.java new file mode 100644 index 000000000..b4be191f2 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/GenericDataResponse.java @@ -0,0 +1,36 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 03.11.15. + */ +public class GenericDataResponse extends DataResponse { + private final PID pid; + private final int[] processedData; + private final byte[] rawData; + + public GenericDataResponse(PID pid, int[] processedData, byte[] rawData) { + this.pid = pid; + this.processedData = processedData; + this.rawData = rawData; + } + + public PID getPid() { + return pid; + } + + @Override + public Number getValue() { + return null; + } + + public int[] getProcessedData() { + return processedData; + } + + public byte[] getRawData() { + return rawData; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/IntakeAirTemperatureResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/IntakeAirTemperatureResponse.java new file mode 100644 index 000000000..ce4ee72c0 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/IntakeAirTemperatureResponse.java @@ -0,0 +1,24 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class IntakeAirTemperatureResponse extends DataResponse { + private final int value; + + public IntakeAirTemperatureResponse(int v) { + this.value = v; + } + + public Number getValue() { + return value; + } + + @Override + public PID getPid() { + return PID.INTAKE_AIR_TEMP; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/IntakeManifoldAbsolutePressureResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/IntakeManifoldAbsolutePressureResponse.java new file mode 100644 index 000000000..f64a2e524 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/IntakeManifoldAbsolutePressureResponse.java @@ -0,0 +1,24 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class IntakeManifoldAbsolutePressureResponse extends DataResponse { + private final int value; + + public IntakeManifoldAbsolutePressureResponse(int v) { + this.value = v; + } + + public Number getValue() { + return value; + } + + @Override + public PID getPid() { + return PID.INTAKE_MAP; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LambdaProbeCurrentResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LambdaProbeCurrentResponse.java new file mode 100644 index 000000000..6a8dbfbf9 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LambdaProbeCurrentResponse.java @@ -0,0 +1,45 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class LambdaProbeCurrentResponse extends DataResponse { + private final double current; + private final double equivalenceRatio; + + public LambdaProbeCurrentResponse(double current, double er) { + this.current = current; + this.equivalenceRatio = er; + } + + public double getCurrent() { + return current; + } + + public double getEquivalenceRatio() { + return equivalenceRatio; + } + + @Override + public PID getPid() { + return PID.O2_LAMBDA_PROBE_1_CURRENT; + } + + @Override + public Number getValue() { + return getEquivalenceRatio(); + } + + @Override + public boolean isComposite() { + return true; + } + + @Override + public Number[] getCompositeValues() { + return new Number[] {equivalenceRatio, current}; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LambdaProbeVoltageResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LambdaProbeVoltageResponse.java new file mode 100644 index 000000000..afe8f4ea2 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LambdaProbeVoltageResponse.java @@ -0,0 +1,54 @@ +package org.envirocar.obd.commands.response.entity; + +import com.google.common.base.MoreObjects; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class LambdaProbeVoltageResponse extends DataResponse { + private final double voltage; + private final double equivalenceRatio; + + public LambdaProbeVoltageResponse(double voltage, double er) { + this.voltage = voltage; + this.equivalenceRatio = er; + } + + public double getVoltage() { + return voltage; + } + + public double getEquivalenceRatio() { + return equivalenceRatio; + } + + @Override + public PID getPid() { + return PID.O2_LAMBDA_PROBE_1_VOLTAGE; + } + + @Override + public Number getValue() { + return getEquivalenceRatio(); + } + + @Override + public boolean isComposite() { + return true; + } + + @Override + public Number[] getCompositeValues() { + return new Number[] {equivalenceRatio, voltage}; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("equivalenceRatio", equivalenceRatio) + .add("voltage", voltage).toString(); + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LongTermFuelTrimResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LongTermFuelTrimResponse.java new file mode 100644 index 000000000..be7cef731 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/LongTermFuelTrimResponse.java @@ -0,0 +1,30 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 02.11.15. + */ +public class LongTermFuelTrimResponse extends DataResponse { + private final double value; + private final int bank; + + public LongTermFuelTrimResponse(double v, int bank) { + this.value = v; + this.bank = bank; + } + + public Number getValue() { + return value; + } + + public int getBank() { + return bank; + } + + @Override + public PID getPid() { + return PID.LONG_TERM_FUEL_TRIM_BANK_1; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/MAFResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/MAFResponse.java new file mode 100644 index 000000000..1659e9a65 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/MAFResponse.java @@ -0,0 +1,24 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class MAFResponse extends DataResponse { + private final float value; + + public MAFResponse(float v) { + this.value = v; + } + + public Number getValue() { + return value; + } + + @Override + public PID getPid() { + return PID.MAF; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/ShortTermFuelTrimResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/ShortTermFuelTrimResponse.java new file mode 100644 index 000000000..71a15f903 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/ShortTermFuelTrimResponse.java @@ -0,0 +1,30 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 02.11.15. + */ +public class ShortTermFuelTrimResponse extends DataResponse { + private final double value; + private final int bank; + + public ShortTermFuelTrimResponse(double v, int bank) { + this.value = v; + this.bank = bank; + } + + public Number getValue() { + return value; + } + + public int getBank() { + return bank; + } + + @Override + public PID getPid() { + return PID.SHORT_TERM_FUEL_TRIM_BANK_1; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/SpeedResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/SpeedResponse.java new file mode 100644 index 000000000..3583ce5f3 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/SpeedResponse.java @@ -0,0 +1,24 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class SpeedResponse extends DataResponse { + private final int value; + + public SpeedResponse(int v) { + this.value = v; + } + + public Number getValue() { + return value; + } + + @Override + public PID getPid() { + return PID.SPEED; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/ThrottlePositionResponse.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/ThrottlePositionResponse.java new file mode 100644 index 000000000..2238ca377 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/response/entity/ThrottlePositionResponse.java @@ -0,0 +1,24 @@ +package org.envirocar.obd.commands.response.entity; + +import org.envirocar.obd.commands.PID; +import org.envirocar.obd.commands.response.DataResponse; + +/** + * Created by matthes on 30.10.15. + */ +public class ThrottlePositionResponse extends DataResponse { + private final int value; + + public ThrottlePositionResponse(int v) { + this.value = v; + } + + public Number getValue() { + return value; + } + + @Override + public PID getPid() { + return PID.TPS; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/events/PropertyKeyEvent.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/events/PropertyKeyEvent.java new file mode 100644 index 000000000..17345281b --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/events/PropertyKeyEvent.java @@ -0,0 +1,29 @@ +package org.envirocar.obd.events; + +import org.envirocar.core.entity.Measurement; + +public class PropertyKeyEvent implements Timestamped { + + private final Measurement.PropertyKey propertyKey; + private final Number value; + private final long timestamp; + + public PropertyKeyEvent(Measurement.PropertyKey propertyKey, Number value, long timestamp) { + this.propertyKey = propertyKey; + this.value = value; + this.timestamp = timestamp; + } + + public Measurement.PropertyKey getPropertyKey() { + return propertyKey; + } + + public Number getValue() { + return value; + } + + @Override + public long getTimestamp() { + return timestamp; + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/events/Timestamped.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/events/Timestamped.java new file mode 100644 index 000000000..1041ec5e9 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/events/Timestamped.java @@ -0,0 +1,7 @@ +package org.envirocar.obd.events; + +public interface Timestamped { + + long getTimestamp(); + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/AdapterFailedException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AdapterFailedException.java similarity index 96% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/AdapterFailedException.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AdapterFailedException.java index e475918ff..bf461f639 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/AdapterFailedException.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AdapterFailedException.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.protocol.exception; +package org.envirocar.obd.exception; import java.util.concurrent.RejectedExecutionException; diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AdapterSearchingException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AdapterSearchingException.java new file mode 100644 index 000000000..e258e5ce8 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AdapterSearchingException.java @@ -0,0 +1,7 @@ +package org.envirocar.obd.exception; + +/** + * Created by matthes on 29.10.15. + */ +public class AdapterSearchingException extends Exception { +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/AllAdaptersFailedException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AllAdaptersFailedException.java similarity index 95% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/AllAdaptersFailedException.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AllAdaptersFailedException.java index 52c8b2779..1f717733e 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/AllAdaptersFailedException.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/AllAdaptersFailedException.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.protocol.exception; +package org.envirocar.obd.exception; public class AllAdaptersFailedException extends Exception { /** diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/ConnectionLostException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/ConnectionLostException.java similarity index 95% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/ConnectionLostException.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/exception/ConnectionLostException.java index a1af72908..2f220d03f 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/ConnectionLostException.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/ConnectionLostException.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.protocol.exception; +package org.envirocar.obd.exception; public class ConnectionLostException extends Exception { public ConnectionLostException(String string) { diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/MemoryOff.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/InvalidCommandResponseException.java similarity index 73% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/commands/MemoryOff.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/exception/InvalidCommandResponseException.java index d62a81355..c552ea60e 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/commands/MemoryOff.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/InvalidCommandResponseException.java @@ -16,22 +16,19 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.commands; +package org.envirocar.obd.exception; +public class InvalidCommandResponseException extends Exception { -/** - * This command will turn-off memory. - */ -public class MemoryOff extends StringResultCommand { + private final String command; - public MemoryOff() { - super("AT M0"); + public InvalidCommandResponseException(String command) { + super(command); + this.command = command; } - - @Override - public String getCommandName() { - return "Memory Off"; + public String getCommand() { + return command; } -} \ No newline at end of file +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/LooperStoppedException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/LooperStoppedException.java similarity index 95% rename from org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/LooperStoppedException.java rename to org.envirocar.obd/src/main/java/org/envirocar/obd/exception/LooperStoppedException.java index bc22c2557..2a6713ade 100644 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/LooperStoppedException.java +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/LooperStoppedException.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ -package org.envirocar.obd.protocol.exception; +package org.envirocar.obd.exception; public class LooperStoppedException extends RuntimeException { diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/NoDataReceivedException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/NoDataReceivedException.java new file mode 100644 index 000000000..10bf68faf --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/NoDataReceivedException.java @@ -0,0 +1,13 @@ +package org.envirocar.obd.exception; + +/** + * Created by matthes on 29.10.15. + */ +public class NoDataReceivedException extends Exception { + + + public NoDataReceivedException(String s) { + super(s); + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/StreamFinishedException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/StreamFinishedException.java new file mode 100644 index 000000000..78aaa24a4 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/StreamFinishedException.java @@ -0,0 +1,11 @@ +package org.envirocar.obd.exception; + +/** + * Created by matthes on 07.11.15. + */ +public class StreamFinishedException extends Throwable { + + public StreamFinishedException(String s) { + super(s); + } +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/UnmatchedResponseException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/UnmatchedResponseException.java new file mode 100644 index 000000000..b43a38275 --- /dev/null +++ b/org.envirocar.obd/src/main/java/org/envirocar/obd/exception/UnmatchedResponseException.java @@ -0,0 +1,12 @@ +package org.envirocar.obd.exception; + +/** + * Created by matthes on 29.10.15. + */ +public class UnmatchedResponseException extends Exception { + + public UnmatchedResponseException() { + super("no further information available"); + } + +} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AbstractAsynchronousConnector.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AbstractAsynchronousConnector.java deleted file mode 100644 index 2bec09783..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AbstractAsynchronousConnector.java +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol; - -import org.envirocar.core.logging.Logger; -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.protocol.exception.AdapterFailedException; -import org.envirocar.obd.protocol.exception.ConnectionLostException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collections; -import java.util.List; - -public abstract class AbstractAsynchronousConnector implements OBDConnector { - - private static final Logger logger = Logger.getLogger(AbstractAsynchronousConnector.class); - private InputStream inputStream; - private OutputStream outputStream; - private AsynchronousResponseThread responseThread; - - protected abstract List getRequestCommands(); - - protected abstract List getInitializationCommands(); - - protected abstract char getRequestEndOfLine(); - - protected abstract ResponseParser getResponseParser(); - - protected abstract long getSleepTimeBetweenCommands(); - - @Override - public void provideStreamObjects(InputStream inputStream, - OutputStream outputStream) { - this.inputStream = inputStream; - this.outputStream = outputStream; - - startResponseThread(); - } - - @Override - public void executeInitializationCommands() throws IOException, - AdapterFailedException { - for (CommonCommand cmd : getInitializationCommands()) { - try { - Thread.sleep(250); - } catch (InterruptedException e) { - logger.warn(e.getMessage(), e); - } - - executeCommand(cmd); - } - } - - - @Override - public List executeRequestCommands() throws IOException, - AdapterFailedException, ConnectionLostException { - long sleep = getSleepTimeBetweenCommands(); - for (CommonCommand cmd : getRequestCommands()) { - executeCommand(cmd); - - if (sleep > 0) { - try { - Thread.sleep(sleep); - } catch (InterruptedException e) { - logger.warn(e.getMessage(), e); - } - } - } - - if (responseThread != null) { - if (responseThread.isRunning()) { - return responseThread.pullAvailableCommands(); - } - else { - throw new ConnectionLostException("ResponseThread has been shutdown"); - } - } - return Collections.emptyList(); - } - - private void executeCommand(CommonCommand cmd) throws IOException { - logger.debug("Sending command: "+cmd.getCommandName()); - - byte[] bytes = cmd.getOutgoingBytes(); - if (bytes != null && bytes.length > 0) { - outputStream.write(bytes); - } - outputStream.write(getRequestEndOfLine()); - outputStream.flush(); - } - - @Override - public void prepareShutdown() { - if (responseThread != null) { - responseThread.shutdown(); - } - } - - @Override - public void shutdown() { - if (responseThread != null) { - try { - responseThread.join(); - } catch (InterruptedException e) { - logger.warn(e.getMessage(), e); - } - } - } - - protected void startResponseThread() { - if (responseThread == null || !responseThread.isRunning()) { - responseThread = new AsynchronousResponseThread(inputStream, getResponseParser()); - responseThread.start(); - } - } - - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AbstractSequentialConnector.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AbstractSequentialConnector.java deleted file mode 100644 index db6c4677f..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AbstractSequentialConnector.java +++ /dev/null @@ -1,556 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol; - -import android.util.Base64; - -import org.envirocar.core.logging.Logger; -import org.envirocar.obd.FeatureFlags; -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.commands.CommonCommand.CommonCommandState; -import org.envirocar.obd.commands.EngineLoad; -import org.envirocar.obd.commands.FuelSystemStatus; -import org.envirocar.obd.commands.IntakePressure; -import org.envirocar.obd.commands.IntakeTemperature; -import org.envirocar.obd.commands.LongTermTrimBank1; -import org.envirocar.obd.commands.MAF; -import org.envirocar.obd.commands.O2LambdaProbe; -import org.envirocar.obd.commands.PIDSupported; -import org.envirocar.obd.commands.PIDUtil; -import org.envirocar.obd.commands.PIDUtil.PID; -import org.envirocar.obd.commands.RPM; -import org.envirocar.obd.commands.ShortTermTrimBank1; -import org.envirocar.obd.commands.Speed; -import org.envirocar.obd.commands.TPS; -import org.envirocar.obd.protocol.exception.AdapterFailedException; -import org.envirocar.obd.protocol.exception.ConnectionLostException; -import org.envirocar.obd.protocol.exception.UnmatchedCommandResponseException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * This class acts as the basis for adapters which work - * in a request/response fashion (in particular, they do not - * send out data without an explicit request) - * - * @author matthes rieke - */ -public abstract class AbstractSequentialConnector implements OBDConnector { - private static final Logger logger = Logger.getLogger(AbstractSequentialConnector.class.getName()); - private static final int SLEEP_TIME = 25; - private static final int MAX_SLEEP_TIME = 5000; - private static final int MAX_INVALID_RESPONSE_COUNT = 5; - private static final int MIN_BACKLIST_COUNT = 5; - private static final int MAX_SEARCHING_COUNT_IN_A_ROW = 10; - private static Set whitelistedCommandNames = new HashSet(); - - private InputStream inputStream; - private OutputStream outputStream; - private boolean connectionEstablished; - private boolean staleConnection; - private int invalidResponseCount; - private Map blacklistCandidates = new HashMap(); - private Set blacklistedCommandNames = new HashSet(); - - private int searchingCountInARow; - private Set supportedPIDs; - private int cycle = 0; - private String preferredLambdaProbe; - private ExecutorService initializationExecutor = Executors.newSingleThreadExecutor(); - - static { -// whitelistedCommandNames.add(new FuelSystemStatus().getCommandName()); -// whitelistedCommandNames.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_1_VOLTAGE).getCommandName()); - } - - /** - * @return the list of initialization commands for the adapter - */ - protected abstract List getInitializationCommands(); - - /** - * a sub-class shall process the given command and determine - * the connection state when a specific set of command results - * have been rececived. - * - * @param cmd the executed command - */ - protected abstract void processInitializationCommand(CommonCommand cmd); - - @Override - public abstract boolean supportsDevice(String deviceName); - - @Override - public abstract ConnectionState connectionState(); - - - @Override - public void provideStreamObjects(InputStream inputStream, - OutputStream outputStream) { - this.inputStream = inputStream; - this.outputStream = outputStream; - } - - protected List getRequestCommands() { - List requestCommands; - if (supportedPIDs != null && supportedPIDs.size() != 0) { - requestCommands = new ArrayList(); - for (PID pid : supportedPIDs) { - CommonCommand cmd = PIDUtil.instantiateCommand(pid); - if (cmd != null) { - requestCommands.add(cmd); - } - } - - logger.info("PID supported result: "+requestCommands); - } else { - requestCommands = new ArrayList(); - requestCommands.add(new Speed()); - requestCommands.add(new MAF()); - requestCommands.add(new RPM()); - requestCommands.add(new IntakePressure()); - requestCommands.add(new IntakeTemperature()); - requestCommands.add(new EngineLoad()); - requestCommands.add(new TPS()); - } - - // TODO reintegrate, fix -// /* -// * XXX: Tryout for Lambda probes. better: do via PIDSupported -// * -// */ -// if (this.preferredLambdaProbe == null || this.preferredLambdaProbe.isEmpty()) { -// if (cycle % 8 == 0) { -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_1_VOLTAGE)); -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_1_CURRENT)); -// } -// else if (cycle % 8 == 1) { -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_2_VOLTAGE)); -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_2_CURRENT)); -// } -// else if (cycle % 8 == 2) { -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_3_VOLTAGE)); -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_3_CURRENT)); -// } -// else if (cycle % 8 == 3) { -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_4_VOLTAGE)); -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_4_CURRENT)); -// } -// else if (cycle % 8 == 4) { -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_5_VOLTAGE)); -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_5_CURRENT)); -// } -// else if (cycle % 8 == 5) { -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_6_VOLTAGE)); -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_6_CURRENT)); -// } -// else if (cycle % 8 == 6) { -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_7_VOLTAGE)); -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_7_CURRENT)); -// } -// else if (cycle % 8 == 7) { -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_8_VOLTAGE)); -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PID.O2_LAMBDA_PROBE_8_CURRENT)); -// } -// } -// else { -// /* -// * we got one positive response, use that -// */ -// requestCommands.add(O2LambdaProbe.fromPIDEnum(PIDUtil.fromString(preferredLambdaProbe))); -// } - - - requestCommands.add(new FuelSystemStatus()); - requestCommands.add(new ShortTermTrimBank1()); - requestCommands.add(new LongTermTrimBank1()); - - cycle++; - - return requestCommands; - } - - private void onInitializationCommand(CommonCommand cmd) { - if (cmd instanceof PIDSupported && FeatureFlags.usePIDSupported()) { - if (!(cmd.getCommandState() == CommonCommandState.EXECUTION_ERROR)) { - this.supportedPIDs = ((PIDSupported) cmd).getSupportedPIDs(); - } - } - processInitializationCommand(cmd); - } - - - private void runCommand(CommonCommand cmd) - throws IOException { - logger.debug("Sending command " +cmd.getCommandName()+ " / "+ new String(cmd.getOutgoingBytes())); - - try { - sendCommand(cmd); - } catch (RuntimeException e) { - logger.warn("Error while sending command '" + cmd.toString() + "': "+e.getMessage(), e); - cmd.setCommandState(CommonCommandState.EXECUTION_ERROR); - return; - } - - if (!cmd.awaitsResults()) return; - - // waiting with InputStream#available() does not work on all devices (and cars?!) -// waitForResult(cmd); - - try { - readResult(cmd); - } catch (RuntimeException e) { - logger.warn("Error while sending command '" + cmd.toString() + "': "+e.getMessage(), e); - cmd.setCommandState(CommonCommandState.EXECUTION_ERROR); - } - } - - private void onBlacklistCandidate(CommonCommand cmd) { - String name = cmd.getCommandName(); - - if (blacklistedCommandNames.contains(name)) return; - - /* - * whiteliste, basically for testing via user study - */ - if (whitelistedCommandNames.contains(name)) return; - - AtomicInteger candidate = blacklistCandidates.get(name); - - if (candidate != null) { - int count = candidate.incrementAndGet(); - if (count > MIN_BACKLIST_COUNT) { - logger.info("Blacklisting command: "+name); - blacklistedCommandNames.add(name); - } - } - else { - blacklistCandidates.put(name, new AtomicInteger(0)); - } - } - - /** - * Sends the OBD-II request. - * - * This method may be overriden in subclasses, such as ObMultiCommand or - * TroubleCodesObdCommand. - * - * @param cmd - * The command to send. - */ - private void sendCommand(CommonCommand cmd) throws IOException { - // write to OutputStream, or in this case a BluetoothSocket - outputStream.write(cmd.getOutgoingBytes()); - outputStream.write(cmd.getEndOfLineSend()); - outputStream.flush(); - } - - /** - * @deprecated some devices (and cars?!) do not implement #available() - * reliably - */ - @SuppressWarnings("unused") - @Deprecated - private void waitForResult(CommonCommand cmd) throws IOException { -// try { -// Thread.sleep(SLEEP_TIME); -// } catch (InterruptedException e) { -// logger.warn(e.getMessage(), e); -// } - - if (!cmd.awaitsResults()) return; - try { - int tries = 0; - while (inputStream.available() <= 0) { - if (tries++ * getSleepTime() > getMaxTimeout()) { - if (cmd.responseAlwaysRequired()) { - throw new IOException("OBD-II Request Timeout of "+ getMaxTimeout() +" ms exceeded."); - } - else { - return; - } - } - - Thread.sleep(getSleepTime()); - } - - } catch (InterruptedException e) { - logger.warn(e.getMessage(), e); - } - } - - /** - * Reads the OBD-II response. - * @param cmd - */ - private void readResult(CommonCommand cmd) throws IOException { - byte[] rawData = readResponseLine(cmd); - cmd.setRawData(rawData); - cmd.setResultTime(System.currentTimeMillis()); - - // read string each two chars - cmd.parseRawData(); - } - - private byte[] readResponseLine(CommonCommand cmd) throws IOException { - byte b = 0; - - Set ignored = cmd.getIgnoredChars(); - - byte[] buffer = new byte[272]; - int index = 0; - // read until '>' arrives - while (index < buffer.length && (char) (b = (byte) inputStream.read()) != cmd.getEndOfLineReceive()) { - if (!ignored.contains((char) b)){ - buffer[index++] = b; - } - } - - if (index > 0) { - logger.verbose("Response read. Data (base64): "+ - Base64.encodeToString(buffer, 0, index, Base64.DEFAULT)); - } - - return Arrays.copyOf(buffer, index); - } - - - public int getMaxTimeout() { - return MAX_SLEEP_TIME; - } - - public int getSleepTime() { - return SLEEP_TIME; - } - - @Override - public void executeInitializationCommands() throws IOException, AdapterFailedException { - final List cmds = this.getInitializationCommands(); - - if (initializationExecutor.isShutdown() || initializationExecutor.isTerminated()) { - throw new AdapterFailedException(getClass().getSimpleName()); - } - - Future future; - try { - future = initializationExecutor.submit(new Callable() { - - @Override - public Boolean call() throws Exception { - try { - executeCommands(cmds); - executeCommand(new PIDSupported()); - return true; - } catch (UnmatchedCommandResponseException e) { - logger.warn("This should never happen!", e); - } catch (ConnectionLostException e) { - logger.warn("This should never happen!", e); - } - return false; - } - }); - } - catch (RejectedExecutionException e) { - throw new AdapterFailedException(getClass().getSimpleName(), e); - } - - try { - Boolean resp = future.get(10, TimeUnit.SECONDS); - - if (!resp.booleanValue()) { - throw new AdapterFailedException("Init commands took too long."); - } - - } catch (InterruptedException e) { - throw new AdapterFailedException(e.getMessage()); - } catch (ExecutionException e) { - throw new AdapterFailedException(e.getMessage()); - } catch (TimeoutException e) { - throw new AdapterFailedException(e.getMessage()); - } - - } - - @Override - public List executeRequestCommands() throws IOException, AdapterFailedException, ConnectionLostException { - List list = getRequestCommands(); - - for (CommonCommand cmd : list) { - if (blacklistedCommandNames.contains(cmd.getCommandName())) { - /* - * we have received enough failed responses for this command - */ - continue; - } - - try { - executeCommand(cmd); - } catch (UnmatchedCommandResponseException e) { - logger.warn("Unmatched Response detected! trying to read another line."); - readResponseLine(cmd); - } - - /* - * check if we got a positive response from a Lambda probe request - */ - if (cmd.getCommandState() == CommonCommandState.FINISHED) { - evaluateSupportedLambdaCommand(cmd); - } - } - - return list; - } - - private void evaluateSupportedLambdaCommand(CommonCommand cmd) { - if (this.preferredLambdaProbe != null && !this.preferredLambdaProbe.isEmpty()) { - /* - * no action required, we already got what we want - */ - return; - } - - if (cmd instanceof O2LambdaProbe) { - this.preferredLambdaProbe = ((O2LambdaProbe) cmd).getPID(); - } - } - - /** - * Execute a list of commands - * - * @throws AdapterFailedException if the adapter could not establish a connection - * @throws IOException if an exception occurred while accessing the stream objects - * @throws UnmatchedCommandResponseException if the response did not match the requested command - * @throws ConnectionLostException if the maximum number of unmatched responses exceeded - */ - private void executeCommands(List cmds) throws AdapterFailedException, IOException, UnmatchedCommandResponseException, ConnectionLostException { - for (CommonCommand c : cmds) { - executeCommand(c); - } - } - - /** - * Execute one command using the given stream objects - * - * @throws AdapterFailedException if the adapter could not establish a connection - * @throws IOException if an exception occurred while accessing the stream objects - * @throws UnmatchedCommandResponseException if the response did not match the requested command - * @throws ConnectionLostException if the maximum number of unmatched responses exceeded - */ - private void executeCommand(CommonCommand cmd) throws AdapterFailedException, IOException, UnmatchedCommandResponseException, ConnectionLostException { - try { - if (cmd.getCommandState().equals(CommonCommandState.NEW)) { - - // Run the job - cmd.setCommandState(CommonCommandState.RUNNING); - runCommand(cmd); - } - } catch (IOException e) { - if (!connectionEstablished) { - /* - * lets first try a different adapter before we fail! - */ - logger.warn(e.getMessage(), e); - throw new AdapterFailedException(getClass().getName()); - } else { - throw e; - } - } - - if (cmd != null) { - if (!cmd.awaitsResults()) return; - - switch (cmd.getCommandState()) { - case FINISHED: - if (!connectionEstablished) { - onInitializationCommand(cmd); - if (connectionState() == ConnectionState.CONNECTED) { - connectionEstablished = true; - } - } - else { - if (cmd instanceof PIDSupported) { - onInitializationCommand(cmd); - } - if (staleConnection) { - staleConnection = false; - invalidResponseCount = 0; - searchingCountInARow = 0; - } - } - break; - case EXECUTION_ERROR: - String raw = cmd.getRawData() == null ? "null" : new String(cmd.getRawData()); - logger.debug("Execution Error for " +cmd.getCommandName() +": "+raw); - this.onBlacklistCandidate(cmd); - break; - - case SEARCHING: - logger.info("Adapter searching. Continuing. Response for " +cmd.getCommandName() +": "+new String(cmd.getRawData())); - staleConnection = true; - - if (searchingCountInARow++ > MAX_SEARCHING_COUNT_IN_A_ROW) { - throw new ConnectionLostException("Adapter is SEARCHING mode for too long."); - } - - break; - case UNMATCHED_RESULT: - logger.warn("Did not receive the expected result! Expected: "+cmd.getResponseTypeID()); - - if (staleConnection && invalidResponseCount++ > MAX_INVALID_RESPONSE_COUNT) { - throw new ConnectionLostException("Received too many unmatched responses."); - } - else { - staleConnection = true; - throw new UnmatchedCommandResponseException(); - } - default: - break; - } - - } - - } - - @Override - public void shutdown() { - if (initializationExecutor != null) { - initializationExecutor.shutdown(); - } - } - - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AsynchronousResponseThread.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AsynchronousResponseThread.java deleted file mode 100644 index f9d916016..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/AsynchronousResponseThread.java +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol; - -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; - -import org.envirocar.core.logging.Logger; -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.protocol.exception.LooperStoppedException; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -public class AsynchronousResponseThread extends HandlerThread { - - private static final Logger logger = Logger.getLogger(AsynchronousResponseThread.class); - private static final int MAX_BUFFER_SIZE = 32; - private Handler handler; - private InputStream inputStream; - - private Runnable readInputStreamRunnable; - - private List buffer = new ArrayList(); - - protected boolean running = true; - private byte[] globalBuffer = new byte[64]; - private int globalIndex; - private ResponseParser responseParser; - - public AsynchronousResponseThread(final InputStream in, ResponseParser responseParser) { - super("AsynchronousResponseThread"); - this.inputStream = in; - - this.responseParser = responseParser; - - this.readInputStreamRunnable = new Runnable() { - - @Override - public void run() { - while (running) { - - CommonCommand cmd; - try { - cmd = readResponse(); - - if (cmd != null) { - synchronized (AsynchronousResponseThread.this) { - buffer.add(cmd); - } - } - - } catch (IOException e) { - logger.warn(e.getMessage(), e); - break; - } - } - - throw new LooperStoppedException(); - } - }; - } - - private CommonCommand readResponse() throws IOException { - byte byteIn; - int intIn; - while (running) { - intIn = inputStream.read(); - - if (intIn < 0) { - break; - } - - byteIn = (byte) intIn; - - if (byteIn == (byte) responseParser.getEndOfLine()) { - boolean isReplete = false; - synchronized (this) { - /* - * are we fed? - */ - isReplete = buffer.size() > MAX_BUFFER_SIZE; - } - - CommonCommand result = null; - if (!isReplete) { - result = responseParser.processResponse(globalBuffer, - 0, globalIndex); - } - - globalIndex = 0; - return result; - } else { - globalBuffer[globalIndex++] = byteIn; - } - } - - return null; - } - - - @Override - public void run() { - Looper.prepare(); - handler = new Handler(); - handler.post(readInputStreamRunnable); - try { - Looper.loop(); - } catch (LooperStoppedException e) { - logger.info("AsynchronousResponseThread stopped."); - } - } - - public List pullAvailableCommands() { - List result; - synchronized (this) { - result = new ArrayList(buffer.size()); - result.addAll(buffer); - buffer.clear(); - } - return result; - } - - public void shutdown() { - logger.info("SHUTDOWN!"); - running = false; - } - - public boolean isRunning() { - return running; - } - - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/OBDCommandLooper.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/OBDCommandLooper.java deleted file mode 100644 index ff648462b..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/OBDCommandLooper.java +++ /dev/null @@ -1,462 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol; - -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; - -import org.envirocar.core.logging.Logger; -import org.envirocar.obd.Listener; -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.commands.CommonCommand.CommonCommandState; -import org.envirocar.obd.protocol.drivedeck.DriveDeckSportConnector; -import org.envirocar.obd.protocol.exception.AdapterFailedException; -import org.envirocar.obd.protocol.exception.AllAdaptersFailedException; -import org.envirocar.obd.protocol.exception.ConnectionLostException; -import org.envirocar.obd.protocol.exception.LooperStoppedException; -import org.envirocar.obd.protocol.sequential.AposW3Connector; -import org.envirocar.obd.protocol.sequential.ELM327Connector; -import org.envirocar.obd.protocol.sequential.OBDLinkMXConnector; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * this is the main class for interacting with a OBD-II adapter. - * It takes {@link InputStream} and {@link OutputStream} objects - * to do the actual raw communication. A {@link Listener} is provided - * with updates. The {@link ConnectionListener} will get informed on - * certain changes in the connection state. - * - * Initialize this class and simply use the {@link #start()} and {@link #stopLooper()} - * methods to manage its state. - * - * @author matthes rieke - * - */ -public class OBDCommandLooper extends HandlerThread { - - private enum Phase { - INITIALIZATION, - COMMAND_EXECUTION - } - - private static final Logger logger = Logger.getLogger(OBDCommandLooper.class); - protected static final long ADAPTER_TRY_PERIOD = 5000; - private static final Integer MAX_PHASE_COUNT = 2; - public static final long MAX_NODATA_TIME = 1000 * 60 * 1; - - private List adapterCandidates = new ArrayList(); - private OBDConnector obdAdapter; - private Listener commandListener; - private InputStream inputStream; - private OutputStream outputStream; - private Handler commandExecutionHandler; - protected boolean running = true; - protected boolean connectionEstablished = false; - protected long requestPeriod = 100; - private int tries; - private int adapterIndex; - private ConnectionListener connectionListener; - private String deviceName; - private Map phaseCountMap = new HashMap(); - private MonitorRunnable monitor; - private long lastSuccessfulCommandTime; - private boolean userRequestedStop; - - private Runnable commonCommandsRunnable = new Runnable() { - public void run() { - if (!running) { - logger.info("Exiting commandHandler."); - throw new LooperStoppedException(); - } - - try { - executeCommandRequests(); - } catch (IOException e) { - running = false; - if (!userRequestedStop) { - connectionListener.requestConnectionRetry(e); - } - logger.info("Exiting commandHandler."); - throw new LooperStoppedException(); - } - - if (!running) { - logger.info("Exiting commandHandler."); - throw new LooperStoppedException(); - } - - commandExecutionHandler.postDelayed(commonCommandsRunnable, requestPeriod); - } - }; - - - private Runnable initializationCommandsRunnable = new Runnable() { - public void run() { - if (running && !connectionEstablished) { - /* - * an async connector will probably only verify its connection - * after one try cycle of executeInitializationRequests. - */ - if (obdAdapter != null && obdAdapter.connectionState() == OBDConnector - .ConnectionState.CONNECTED) { - connectionEstablished(); - return; - } - - try { - selectAdapter(); - } catch (AllAdaptersFailedException e) { - running = false; - connectionListener.onAllAdaptersFailed(); - throw new LooperStoppedException(); - } - - String stmt = "Trying "+obdAdapter.getClass().getSimpleName() +"."; - logger.info(stmt); - connectionListener.onStatusUpdate(stmt); - - try { - executeInitializationRequests(); - } catch (IOException e) { - running = false; - if (!userRequestedStop) { - connectionListener.requestConnectionRetry(e); - } - logger.info("Exiting commandHandler."); - throw new LooperStoppedException(); - } catch (AdapterFailedException e) { - logger.warn(e.getMessage()); - } - - /* - * a sequential connector might already have a satisfied - * connection - */ - if (obdAdapter != null && obdAdapter.connectionState() == OBDConnector - .ConnectionState.CONNECTED) { - connectionEstablished(); - return; - } - - if (!running) { - logger.info("Exiting commandHandler."); - throw new LooperStoppedException(); - } - - commandExecutionHandler.postDelayed(initializationCommandsRunnable, ADAPTER_TRY_PERIOD); - } - - if (!running) { - throw new LooperStoppedException(); - } - } - - }; - - /** - * same as OBDCommandLooper#OBDCommandLooper(InputStream, OutputStream, Object, Listener, ConnectionListener, int) with NORM_PRIORITY - */ - public OBDCommandLooper(InputStream in, OutputStream out, - String deviceName, Listener l, ConnectionListener cl) { - this(in, out, deviceName, l, cl, NORM_PRIORITY); - } - - - /** - * An application shutting down the streams ({@link InputStream#close()} and - * the like) SHALL synchronize on the inputMutex object when doing so. - * Otherwise, the app might crash. - * - * @param in the inputStream of the connection - * @param out the outputStream of the connection - * @param l the listener which receives command responses - * @param cl the connection listener which receives connection state changes - * @param priority thread priority - * @throws IllegalArgumentException if one of the inputs equals null - */ - public OBDCommandLooper(InputStream in, OutputStream out, - String deviceName, Listener l, ConnectionListener cl, int priority) { - super("OBD-CommandLooper-Handler", priority); - - if (in == null) throw new IllegalArgumentException("in must not be null!"); - if (out == null) throw new IllegalArgumentException("out must not be null!"); - if (l == null) throw new IllegalArgumentException("l must not be null!"); - if (cl == null) throw new IllegalArgumentException("cl must not be null!"); - - this.inputStream = in; - this.outputStream = out; - - this.commandListener = l; - this.connectionListener = cl; - - this.deviceName = deviceName; - - this.phaseCountMap.put(Phase.INITIALIZATION, new AtomicInteger()); - this.phaseCountMap.put(Phase.COMMAND_EXECUTION, new AtomicInteger()); - - } - - private void determinePreferredAdapter(String deviceName) { - for (OBDConnector ac : adapterCandidates) { - if (ac.supportsDevice(deviceName)) { - this.obdAdapter = ac; - break; - } - } - - if (this.obdAdapter == null) { - this.obdAdapter = adapterCandidates.get(0); - } - - this.obdAdapter.provideStreamObjects(inputStream, outputStream); - logger.info("Using "+this.obdAdapter.getClass().getName() +" connector as the preferred adapter."); - } - - - /** - * stop the command looper. this removes all pending commands. - * This object is no longer executable, a new instance has to - * be created. - * - * Only use this if the stop is from high-level (e.g. user request) - * and NOT on any kind of exception - */ - public void stopLooper() { - logger.info("stopping the command execution!"); - this.running = false; - this.userRequestedStop = true; - - if (this.monitor != null) { - this.monitor.running = false; - } - } - - private void executeInitializationRequests() throws IOException, AdapterFailedException { - try { - this.obdAdapter.executeInitializationCommands(); - } catch (IOException e) { - if (!userRequestedStop) { - connectionListener.requestConnectionRetry(e); - } - running = false; - return; - } - - } - - private void executeCommandRequests() throws IOException { - - List cmds; - try { - cmds = this.obdAdapter.executeRequestCommands(); - } catch (ConnectionLostException e) { - switchPhase(Phase.INITIALIZATION, new IOException(e)); - - return; - } catch (AdapterFailedException e) { - logger.severe("This should never happen!", e); - return; - } - - long time = 0; - for (CommonCommand cmd : cmds) { - logger.info("COMMAND: "+cmd.getCommandName() +": "+cmd.getCommandState() +" / "+ Arrays.toString(cmd.getRawData())); - if (cmd.getCommandState() == CommonCommandState.FINISHED) { - commandListener.receiveUpdate(cmd); - time = cmd.getResultTime(); - } - } - - if (time != 0) { - lastSuccessfulCommandTime = time; - } - - } - - - private void switchPhase(Phase phase, IOException reason) { - logger.info("Switching to Phase: " +phase + (reason != null ? " / Reason: "+reason.getMessage() : "")); - - - int phaseCount = phaseCountMap.get(phase).incrementAndGet(); - - commandExecutionHandler.removeCallbacks(initializationCommandsRunnable); - commandExecutionHandler.removeCallbacks(commonCommandsRunnable); - - /* - * if we were too often in the same phase (e.g. init), - * request a reconnect - */ - if (phaseCount >= MAX_PHASE_COUNT) { - logger.warn("Too often in phase: "+phaseCount); - connectionListener.requestConnectionRetry(reason); - - running = false; - return; - } - - switch (phase) { - case INITIALIZATION: - connectionEstablished = false; - obdAdapter = null; - - setupAdapterCandidates(); - - commandExecutionHandler.post(initializationCommandsRunnable); - break; - case COMMAND_EXECUTION: - this.connectionEstablished = true; - this.connectionListener.onConnectionVerified(); - commandExecutionHandler.postDelayed(commonCommandsRunnable, requestPeriod); - commandListener.onConnected(deviceName); - - startMonitoring(); - - break; - default: - break; - } - } - - - - private void startMonitoring() { - if (this.monitor != null) { - this.monitor.running = false; - } - - this.monitor = new MonitorRunnable(); - new Thread(this.monitor).start(); - } - - - private void setupAdapterCandidates() { - adapterCandidates.clear(); - adapterCandidates.add(new ELM327Connector()); - adapterCandidates.add(new AposW3Connector()); - adapterCandidates.add(new OBDLinkMXConnector()); - adapterCandidates.add(new DriveDeckSportConnector()); - } - - - private void connectionEstablished() { - logger.info("OBD Adapter " + this.obdAdapter.getClass().getName() + - " verified the responses. Connection Established!"); - - /* - * switch to common command execution phase - */ - switchPhase(Phase.COMMAND_EXECUTION, null); - } - - - private void selectAdapter() throws AllAdaptersFailedException { - if (this.obdAdapter == null) { - determinePreferredAdapter(deviceName); - this.obdAdapter.provideStreamObjects(inputStream, outputStream); - } - - else if (++tries >= this.obdAdapter.getMaximumTriesForInitialization()) { - if (this.obdAdapter != null) { - this.obdAdapter.prepareShutdown(); - this.obdAdapter.shutdown(); - if (this.obdAdapter.connectionState() == OBDConnector.ConnectionState.CONNECTED) { - /* - * the adapter was sure that it fits the device, so - * we do not need to try others - */ - throw new AllAdaptersFailedException(this.obdAdapter.getClass().getSimpleName()); - } - } - - if (adapterIndex+1 >= adapterCandidates.size()) { - throw new AllAdaptersFailedException(adapterCandidates.toString()); - } - - this.obdAdapter = adapterCandidates.get(adapterIndex++ % adapterCandidates.size()); - this.obdAdapter.provideStreamObjects(inputStream, outputStream); - tries = 0; - } - - if (this.obdAdapter != null) { - this.requestPeriod = this.obdAdapter.getPreferredRequestPeriod(); - } - } - - private class MonitorRunnable implements Runnable { - - private boolean running = true; - - @Override - public void run() { - Thread.currentThread().setName("OBD-Data-Monitor"); - while (running) { - try { - Thread.sleep(MAX_NODATA_TIME / 3); - } catch (InterruptedException e) { - logger.warn(e.getMessage(), e); - } - - if (!running) return; - - if (System.currentTimeMillis() - lastSuccessfulCommandTime > MAX_NODATA_TIME) { - commandExecutionHandler.removeCallbacks(commonCommandsRunnable); - commandExecutionHandler.getLooper().quit(); - - if (OBDCommandLooper.this.obdAdapter != null) { - OBDCommandLooper.this.obdAdapter.shutdown(); - } - - connectionListener.requestConnectionRetry(new IOException("Waited too long for data.")); - return; - } - } - } - - } - - @Override - public void run() { - Looper.prepare(); - logger.info("Command loop started. Hash:"+this.hashCode()); - commandExecutionHandler = new Handler(); - switchPhase(Phase.INITIALIZATION, null); - try { - Looper.loop(); - } catch (LooperStoppedException e) { - logger.info("Command loop stopped. Hash:"+this.hashCode()); - } - - if (this.obdAdapter != null) { - this.obdAdapter.shutdown(); - } - } - - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/OBDConnector.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/OBDConnector.java deleted file mode 100644 index 2944b21cb..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/OBDConnector.java +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol; - -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.protocol.exception.AdapterFailedException; -import org.envirocar.obd.protocol.exception.ConnectionLostException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; - -/** - * Interface for a OBD connector. It can provide device specific - * command requests and initialization sequences. - * - * @author matthes rieke - * - */ -public interface OBDConnector { - - - enum ConnectionState { - - /** - * used to indicate a state when the connector could - * not understand any response received - */ - DISCONNECTED, - - /** - * used to indicate a state when the connector understood - * at least one command. Return this state only if the - * adapter is sure, that it can interact with the device - * - but the device yet did not return measurements - */ - CONNECTED, - - /** - * used to indicate a state where the connector received - * a parseable measurement - */ - VERIFIED - } - - /** - * provide the required stream objects to send and retrieve - * commands. - * - * An implementation shall synchronize on the inputMutex - * when accessing the streams. - * - * @param inputStream - * @param outputStream - */ - void provideStreamObjects(InputStream inputStream, - OutputStream outputStream); - - /** - * An implementation shall return true if it - * might support the given bluetooth device. - * - * @param deviceName the bluetooth device name - * @return if it suggests support for the device - */ - boolean supportsDevice(String deviceName); - - /** - * @return true if the implementation established a meaningful connection - */ - ConnectionState connectionState(); - - /** - * an implementation shall use this method to initialize the connection - * to the underlying obd adapter - * - * @throws IOException if an exception occurred while accessing the stream objects - * @throws AdapterFailedException if the adapter could not establish a connection - */ - void executeInitializationCommands() throws IOException, - AdapterFailedException; - - /** - * an implementation shall execute the commands to retrieve - * the common phenomena - * - * @return the parsed command responses - * @throws IOException if an exception occurred while accessing the stream objects - * @throws AdapterFailedException if the adapter could not establish a connection - * @throws ConnectionLostException if the maximum number of unmatched responses exceeded - */ - List executeRequestCommands() throws IOException, - AdapterFailedException, ConnectionLostException; - - - /** - * an implementation shall prepare the freeing of resources (e.g. set running flags to false) - */ - void prepareShutdown(); - - /** - * an implementation shall free all resources it has created (e.g. threads) - */ - void shutdown(); - - /** - * @return the number of maximum tries an adapter sends out - * the initial set of commands - */ - int getMaximumTriesForInitialization(); - - /** - * @return the time in ms the looper should wait between executing the command batch - */ - long getPreferredRequestPeriod(); - - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/ResponseParser.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/ResponseParser.java deleted file mode 100644 index 95298409a..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/ResponseParser.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol; - -import org.envirocar.obd.commands.CommonCommand; - -public interface ResponseParser { - - - /** - * @param bytes the byte buffer - * @param start offset index - * @param count byte count - * @return the parsed command response - */ - public CommonCommand processResponse(byte[] bytes, int start, int count); - - /** - * @return the end of line character - */ - public char getEndOfLine(); - - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/CarriageReturnCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/CarriageReturnCommand.java deleted file mode 100644 index 4da0713db..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/CarriageReturnCommand.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol.drivedeck; - -import org.envirocar.obd.commands.CommonCommand; - -public class CarriageReturnCommand extends CommonCommand { - - private static final String NAME = "DriveDeck CR"; - - public CarriageReturnCommand() { - super(NAME); - } - - @Override - public void parseRawData() { - - } - - @Override - public String getCommandName() { - return NAME; - } - - @Override - public byte[] getOutgoingBytes() { - return new byte[0]; - } - - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/CycleCommand.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/CycleCommand.java deleted file mode 100644 index b9f41b8b2..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/CycleCommand.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol.drivedeck; - -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.commands.PIDUtil; - -import java.util.List; - -public class CycleCommand extends CommonCommand { - - public static enum PID { - SPEED { - @Override - public String toString() { - return convert("0D"); - } - }, - MAF { - @Override - public String toString() { - return convert("10"); - } - }, - RPM { - @Override - public String toString() { - return convert("0C"); - } - }, - IAP { - @Override - public String toString() { - return convert("0B"); - } - }, - IAT { - @Override - public String toString() { - return convert("0F"); - } - }, - SHORT_TERM_FUEL_TRIM { - @Override - public String toString() { - return convert("06"); - } - }, - LONG_TERM_FUEL_TRIM { - @Override - public String toString() { - return convert("07"); - } - }, - O2_LAMBDA_PROBE_1_VOLTAGE { - @Override - public String toString() { - return convert("24"); - } - }, - O2_LAMBDA_PROBE_1_CURRENT { - @Override - public String toString() { - return convert(PIDUtil.PID.O2_LAMBDA_PROBE_1_CURRENT.toString()); - } - }; - - protected String convert(String string) { - return Integer.toString(incrementBy13(hexToInt(string))); - } - - protected int hexToInt(String string) { - return Integer.valueOf(string, 16); - } - - protected int incrementBy13(int hexToInt) { - return hexToInt + 13; - } - - protected String intToHex(int val) { - String result = Integer.toString(val, 16); - if (result.length() == 1) result = "0"+result; - return "0x".concat(result); - } - } - - private static final String NAME = "a17"; - public static final char RESPONSE_PREFIX_CHAR = 'B'; - public static final char TOKEN_SEPARATOR_CHAR = '<'; - private byte[] bytes; - - - public CycleCommand(List pidList) { - super(NAME); - bytes = new byte[3+pidList.size()]; - byte[] prefix = "a17".getBytes(); - - for (int i = 0; i < prefix.length; i++) { - bytes[i] = prefix[i]; - } - - int i = 0; - for (PID pid : pidList) { - bytes[prefix.length + i++] = (byte) Integer.valueOf(pid.toString()).intValue(); - } - } - - @Override - public void parseRawData() { - - } - - @Override - public String getCommandName() { - return NAME; - } - - public byte[] getOutgoingBytes() { - return bytes; - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/DriveDeckSportConnector.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/DriveDeckSportConnector.java deleted file mode 100644 index 0c513eacb..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/drivedeck/DriveDeckSportConnector.java +++ /dev/null @@ -1,425 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol.drivedeck; - -import android.util.Base64; - -import org.envirocar.core.logging.Logger; -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.commands.CommonCommand.CommonCommandState; -import org.envirocar.obd.commands.IntakePressure; -import org.envirocar.obd.commands.IntakeTemperature; -import org.envirocar.obd.commands.MAF; -import org.envirocar.obd.commands.NumberResultCommand; -import org.envirocar.obd.commands.O2LambdaProbe; -import org.envirocar.obd.commands.PIDSupported; -import org.envirocar.obd.commands.RPM; -import org.envirocar.obd.commands.Speed; -import org.envirocar.obd.protocol.AbstractAsynchronousConnector; -import org.envirocar.obd.protocol.OBDConnector; -import org.envirocar.obd.protocol.ResponseParser; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -public class DriveDeckSportConnector extends AbstractAsynchronousConnector { - - private static final Logger logger = Logger.getLogger(DriveDeckSportConnector.class); - private static final char CARRIAGE_RETURN = '\r'; - static final char END_OF_LINE_RESPONSE = '>'; - private static final long SEND_CYCLIC_COMMAND_DELTA = 2500; - private Protocol protocol; - private String vin; - private CycleCommand cycleCommand; - private ResponseParser responseParser = new LocalResponseParser(); - private OBDConnector.ConnectionState state = OBDConnector.ConnectionState.DISCONNECTED; - public long lastResult; - private Set loggedPids = new HashSet(); - - private int mLastVal = 0; - - private static enum Protocol { - CAN11500, CAN11250, CAN29500, CAN29250, KWP_SLOW, KWP_FAST, ISO9141 - } - - public DriveDeckSportConnector() { - createCycleCommand(); - logger.info("Static CycleCommand: " + new String(cycleCommand.getOutgoingBytes())); - } - - private void createCycleCommand() { - List pidList = new ArrayList(); - pidList.add(CycleCommand.PID.SPEED); - pidList.add(CycleCommand.PID.MAF); - pidList.add(CycleCommand.PID.RPM); - pidList.add(CycleCommand.PID.IAP); - pidList.add(CycleCommand.PID.IAT); - // pidList.add(PID.SHORT_TERM_FUEL_TRIM); - // pidList.add(PID.LONG_TERM_FUEL_TRIM); - pidList.add(CycleCommand.PID.O2_LAMBDA_PROBE_1_VOLTAGE); - pidList.add(CycleCommand.PID.O2_LAMBDA_PROBE_1_CURRENT); - this.cycleCommand = new CycleCommand(pidList); - } - - @Override - public OBDConnector.ConnectionState connectionState() { - return this.state; - } - - private void processDiscoveredControlUnits(String substring) { - logger.info("Discovered CUs... "); - } - - protected void processSupportedPID(byte[] bytes, int start, int count) { - String group = new String(bytes, start + 6, 2); - - if (group.equals("00")) { - /* - * this is the first group containing the PIDs of major interest - */ - PIDSupported pidCmd = new PIDSupported(); - byte[] rawBytes = new byte[12]; - rawBytes[0] = '4'; - rawBytes[1] = '1'; - rawBytes[2] = (byte) pidCmd.getResponseTypeID().charAt(0); - rawBytes[3] = (byte) pidCmd.getResponseTypeID().charAt(1); - int target = 4; - String hexTmp; - for (int i = 9; i < 14; i++) { - if (i == 11) continue; - hexTmp = oneByteToHex(bytes[i + start]); - rawBytes[target++] = (byte) hexTmp.charAt(0); - rawBytes[target++] = (byte) hexTmp.charAt(1); - } - - pidCmd.setRawData(rawBytes); - pidCmd.parseRawData(); - - if (pidCmd.getCommandState() == CommonCommandState.FINISHED) { - logger.info(pidCmd.getSupportedPIDs().toArray().toString()); - } - } - } - - private String oneByteToHex(byte b) { - String result = Integer.toString(b & 0xff, 16).toUpperCase(Locale.US); - if (result.length() == 1) result = "0".concat(result); - return result; - } - - private void processVIN(String vinInt) { - this.vin = vinInt; - logger.info("VIN is: " + this.vin); - - updateConnectionState(); - } - - private void updateConnectionState() { - if (state == ConnectionState.VERIFIED) { - return; - } - - if (protocol != null || vin != null) { - state = ConnectionState.CONNECTED; - } - } - - private void determineProtocol(String protocolInt) { - if (protocolInt == null || protocolInt.trim().isEmpty()) { - return; - } - - int prot; - try { - prot = Integer.parseInt(protocolInt); - } catch (NumberFormatException e) { - logger.warn("NFE: " + e.getMessage()); - return; - } - - switch (prot) { - case 1: - protocol = Protocol.CAN11500; - break; - case 2: - protocol = Protocol.CAN11250; - break; - case 3: - protocol = Protocol.CAN29500; - break; - case 4: - protocol = Protocol.CAN29250; - break; - case 5: - protocol = Protocol.KWP_SLOW; - break; - case 6: - protocol = Protocol.KWP_FAST; - break; - case 7: - protocol = Protocol.ISO9141; - break; - default: - return; - } - - logger.info("Protocol is: " + protocol.toString()); - - updateConnectionState(); - } - - - @Override - public boolean supportsDevice(String deviceName) { - return deviceName.contains("DRIVEDECK") && deviceName.contains("W4"); - } - - - private CommonCommand parsePIDResponse(String pid, - byte[] rawBytes, long now, byte[] debugArray) { - - /* - * resulting HEX values are 0x0d additive to the - * default PIDs of OBD. e.g. RPM = 0x19 = 0x0c + 0x0d - */ - CommonCommand result = null; - if (pid.equals("41")) { - //Speed - result = new Speed(); - } else if (pid.equals("42")) { - //MAF - result = new MAF(); - } else if (pid.equals("52")) { - //IAP - result = new IntakeTemperature(); - } else if (pid.equals("49")) { - //IAT - result = new IntakePressure(); - } else if (pid.equals("40") || pid.equals("51")) { - //RPM - result = new RPM(); - } else if (pid.equals("4D")) { - //TODO the current manual does not provide info on how to - //determine which probe value is returned. - result = O2LambdaProbe.fromPIDEnum(org.envirocar.obd.commands.PIDUtil.PID - .O2_LAMBDA_PROBE_1_VOLTAGE); - } - - oneTimePIDLog(pid, rawBytes); - - if (result != null) { - byte[] rawData = createRawData(rawBytes, result.getResponseTypeID()); - result.setRawData(rawData); - result.parseRawData(); - - if (result instanceof NumberResultCommand && result instanceof Speed) { - NumberResultCommand numberResult = (NumberResultCommand) result; - if (numberResult.getNumberResult() != null) { - int val = numberResult.getNumberResult().intValue(); - if (val - mLastVal > 49) { - logger.warn(String.format( - "Received a speed value of %s. this is probably an " + - "erroneous response. Base64 encoded value: %s", - Integer.toString(val), "" + Arrays.toString(Base64.encode - (debugArray, Base64.DEFAULT)))); - } - mLastVal = val; - } - } - - - if (result.getCommandState() == CommonCommandState.EXECUTION_ERROR || - result.getCommandState() == CommonCommandState.SEARCHING) { - return null; - } - - result.setCommandState(CommonCommandState.FINISHED); - result.setResultTime(now); - this.state = ConnectionState.VERIFIED; - } - - return result; - } - - private void oneTimePIDLog(String pid, byte[] rawBytes) { - if (pid == null || rawBytes == null || rawBytes.length == 0) - return; - - if (!loggedPids.contains(pid)) { - logger.info("First response for PID: " + pid + "; Base64: " + - Base64.encodeToString(rawBytes, Base64.DEFAULT)); - loggedPids.add(pid); - } - } - - private byte[] createRawData(byte[] rawBytes, String type) { - byte[] result = new byte[4 + rawBytes.length * 2]; - byte[] typeBytes = type.getBytes(); - result[0] = (byte) '4'; - result[1] = (byte) '1'; - result[2] = typeBytes[0]; - result[3] = typeBytes[1]; - for (int i = 0; i < rawBytes.length; i++) { - String hex = oneByteToHex(rawBytes[i]); - result[(i * 2) + 4] = (byte) hex.charAt(0); - result[(i * 2) + 1 + 4] = (byte) hex.charAt(1); - } - return result; - } - - - @Override - protected List getRequestCommands() { - if (System.currentTimeMillis() - lastResult > SEND_CYCLIC_COMMAND_DELTA) { - return Collections.singletonList((CommonCommand) cycleCommand); - } else { - return Collections.emptyList(); - } - } - - - @Override - protected char getRequestEndOfLine() { - return CARRIAGE_RETURN; - } - - - @Override - protected ResponseParser getResponseParser() { - return responseParser; - } - - @Override - protected List getInitializationCommands() { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - logger.warn(e.getMessage(), e); - } - - return Collections.singletonList((CommonCommand) new CarriageReturnCommand()); - } - - @Override - public int getMaximumTriesForInitialization() { - return 15; - } - - - public class LocalResponseParser implements ResponseParser { - @Override - public CommonCommand processResponse(byte[] bytes, int start, int count) { - if (count <= 0) return null; - - char type = (char) bytes[start + 0]; - - if (type == CycleCommand.RESPONSE_PREFIX_CHAR) { - if ((char) bytes[start + 4] == CycleCommand.TOKEN_SEPARATOR_CHAR) return null; - - String pid = new String(bytes, start + 1, 2); - - /* - * METADATA Stuff - */ - if (pid.equals("14")) { - logger.debug("Status: CONNECTING"); - } else if (pid.equals("15")) { - processVIN(new String(bytes, start + 3, count - 3)); - } else if (pid.equals("70")) { - /* - * short term fix for #192: disable - */ - // processSupportedPID(bytes, start, count); - } else if (pid.equals("71")) { - processDiscoveredControlUnits(new String(bytes, start + 3, count - 3)); - } else if (pid.equals("31")) { - // engine on - logger.debug("Engine: On"); - } else if (pid.equals("32")) { - // engine off (= RPM < 500) - logger.debug("Engine: Off"); - } else { - if (count < 6) { - logger.warn("the response did only contain " + count + " bytes. For PID " + - "responses 6 are minimum"); - return null; - } - - /* - * A PID response - */ - long now = System.currentTimeMillis(); - logger.verbose("Processing PID Response:" + pid); - - byte[] pidResponseValue = new byte[2]; - int target; - for (int i = start + 4; i <= count + start; i++) { - target = i - (start + 4); - if (target >= pidResponseValue.length) - break; - - if ((char) bytes[i] == CycleCommand.TOKEN_SEPARATOR_CHAR) - break; - - pidResponseValue[target] = bytes[i]; - } - - //TODO remove last arg (copy array) as its for deugging - CommonCommand result = parsePIDResponse(pid, pidResponseValue, now, Arrays - .copyOfRange(bytes, start, 12)); - - if (result != null) { - lastResult = now; - } - - return result; - } - - } else if (type == 'C') { - determineProtocol(new String(bytes, start + 1, count - 1)); - } - - return null; - } - - @Override - public char getEndOfLine() { - return END_OF_LINE_RESPONSE; - } - - - } - - - @Override - protected long getSleepTimeBetweenCommands() { - return 0; - } - - @Override - public long getPreferredRequestPeriod() { - return 500; - } - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/UnmatchedCommandResponseException.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/UnmatchedCommandResponseException.java deleted file mode 100644 index 03855310a..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/exception/UnmatchedCommandResponseException.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol.exception; - -public class UnmatchedCommandResponseException extends Exception { - - /** - * - */ - private static final long serialVersionUID = 1L; - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/AposW3Connector.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/AposW3Connector.java deleted file mode 100644 index 65f4a1c46..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/AposW3Connector.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol.sequential; - -import org.envirocar.core.logging.Logger; -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.commands.EchoOff; -import org.envirocar.obd.commands.LineFeedOff; -import org.envirocar.obd.commands.ObdReset; -import org.envirocar.obd.commands.SelectAutoProtocol; -import org.envirocar.obd.commands.StringResultCommand; -import org.envirocar.obd.commands.Timeout; -import org.envirocar.obd.protocol.OBDConnector; - -import java.util.ArrayList; -import java.util.List; - -public class AposW3Connector extends ELM327Connector { - - private static final Logger logger = Logger.getLogger(AposW3Connector.class); - - @Override - public List getInitializationCommands() { - List result = new ArrayList(); - result.add(new ObdReset()); - result.add(new AposEchoOff()); - result.add(new AposEchoOff()); - result.add(new LineFeedOff()); - result.add(new Timeout(62)); - result.add(new SelectAutoProtocol()); - return result; - } - - @Override - public boolean supportsDevice(String deviceName) { - return deviceName.contains("APOS") && deviceName.contains("OBD_W3"); - } - - @Override - public void processInitializationCommand(CommonCommand cmd) { - - if (cmd instanceof AposEchoOff) { - if (cmd instanceof StringResultCommand) { - String content = ((StringResultCommand) cmd).getStringResult(); - if (content.contains("OK")) { - succesfulCount++; - } - } - } else { - super.processInitializationCommand(cmd); - } - - } - - @Override - public OBDConnector.ConnectionState connectionState() { - if (succesfulCount >= 4) { - return OBDConnector.ConnectionState.CONNECTED; - } - return OBDConnector.ConnectionState.DISCONNECTED; - } - - private static class AposEchoOff extends EchoOff { - - @Override - public byte[] getOutgoingBytes() { - try { - /* - * hack for too fast init requests, - * issue observed with Galaxy Nexus (4.3) and VW Tiguan 2013 - */ - Thread.sleep(250); - } catch (InterruptedException e) { - logger.warn(e.getMessage(), e); - } - return super.getOutgoingBytes(); - } - - @Override - public boolean responseAlwaysRequired() { - return false; - } - - } -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/ELM327Connector.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/ELM327Connector.java deleted file mode 100644 index 47d6f200a..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/ELM327Connector.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol.sequential; - -import org.envirocar.obd.commands.CommonCommand; -import org.envirocar.obd.commands.EchoOff; -import org.envirocar.obd.commands.LineFeedOff; -import org.envirocar.obd.commands.ObdReset; -import org.envirocar.obd.commands.SelectAutoProtocol; -import org.envirocar.obd.commands.StringResultCommand; -import org.envirocar.obd.commands.Timeout; -import org.envirocar.obd.protocol.AbstractSequentialConnector; -import org.envirocar.obd.protocol.OBDConnector; - -import java.util.ArrayList; -import java.util.List; - -public class ELM327Connector extends AbstractSequentialConnector { - - protected int succesfulCount; - - /* - * This is what Torque does: - */ - // addCommandToWaitingList(new Defaults()); - // addCommandToWaitingList(new Defaults()); - // addCommandToWaitingList(new ObdReset()); - // addCommandToWaitingList(new ObdReset()); - // addCommandToWaitingList(new EchoOff()); - // addCommandToWaitingList(new EchoOff()); - // addCommandToWaitingList(new EchoOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new LineFeedOff()); - // addCommandToWaitingList(new SpacesOff()); - // addCommandToWaitingList(new HeadersOff()); - // addCommandToWaitingList(new Defaults()); - // addCommandToWaitingList(new ObdReset()); - // addCommandToWaitingList(new ObdReset()); - // addCommandToWaitingList(new EchoOff()); - // addCommandToWaitingList(new EchoOff()); - // addCommandToWaitingList(new EchoOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new MemoryOff()); - // addCommandToWaitingList(new LineFeedOff()); - // addCommandToWaitingList(new SpacesOff()); - // addCommandToWaitingList(new HeadersOff()); - // addCommandToWaitingList(new SelectAutoProtocol()); - // addCommandToWaitingList(new PIDSupported()); - // addCommandToWaitingList(new EnableHeaders()); - // addCommandToWaitingList(new PIDSupported()); - // addCommandToWaitingList(new HeadersOff()); - - /* - * End Torque - */ - - @Override - public List getInitializationCommands() { - List result = new ArrayList(); - result.add(new ObdReset()); - result.add(new EchoOff()); - result.add(new EchoOff()); - result.add(new LineFeedOff()); - result.add(new Timeout(62)); - result.add(new SelectAutoProtocol()); - return result; - } - - @Override - public boolean supportsDevice(String deviceName) { - return deviceName.contains("OBDII") || deviceName.contains("ELM327"); - } - - @Override - public void processInitializationCommand(CommonCommand cmd) { - if (cmd instanceof StringResultCommand) { - String content = ((StringResultCommand) cmd).getStringResult(); - - if (cmd instanceof EchoOff) { - if (content.contains("ELM327v1.")) { - succesfulCount++; - } - else if (content.contains("ATE0") && content.contains("OK")) { - succesfulCount++; - } - } - - else if (cmd instanceof LineFeedOff) { - if (content.contains("OK")) { - succesfulCount++; - } - } - - else if (cmd instanceof Timeout) { - if (content.contains("OK")) { - succesfulCount++; - } - } - - else if (cmd instanceof SelectAutoProtocol) { - if (content.contains("OK")) { - succesfulCount++; - } - } - } - - } - - @Override - public OBDConnector.ConnectionState connectionState() { - if (succesfulCount >= 5) { - return OBDConnector.ConnectionState.CONNECTED; - } - return OBDConnector.ConnectionState.DISCONNECTED; - } - - @Override - public void shutdown() { - super.shutdown(); - } - - @Override - public int getMaximumTriesForInitialization() { - return 1; - } - - @Override - public void prepareShutdown() { - // TODO Auto-generated method stub - - } - - @Override - public long getPreferredRequestPeriod() { - return 100; - } - - - -} diff --git a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/OBDLinkMXConnector.java b/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/OBDLinkMXConnector.java deleted file mode 100644 index 526e3e023..000000000 --- a/org.envirocar.obd/src/main/java/org/envirocar/obd/protocol/sequential/OBDLinkMXConnector.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd.protocol.sequential; - -public class OBDLinkMXConnector extends ELM327Connector { - - @Override - public boolean supportsDevice(String deviceName) { - return deviceName.equalsIgnoreCase("OBDLink MX"); - } - -} diff --git a/org.envirocar.obd/src/test/java/org/envirocar/obd/ExampleUnitTest.java b/org.envirocar.obd/src/test/java/org/envirocar/obd/ExampleUnitTest.java deleted file mode 100644 index a0a42f704..000000000 --- a/org.envirocar.obd/src/test/java/org/envirocar/obd/ExampleUnitTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.obd; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * To work on unit tests, switch the Test Artifact in the Build Variants view. - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/CacheCarDAO.java b/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/CacheCarDAO.java index 12ff2ed5f..659f19297 100644 --- a/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/CacheCarDAO.java +++ b/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/CacheCarDAO.java @@ -1,26 +1,29 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.remote.dao; +import android.security.keystore.UserNotAuthenticatedException; + import org.envirocar.core.dao.AbstractCacheDAO; import org.envirocar.core.dao.CarDAO; import org.envirocar.core.entity.Car; +import org.envirocar.core.entity.User; import org.envirocar.core.exception.DataCreationFailureException; import org.envirocar.core.exception.DataRetrievalFailureException; import org.envirocar.core.exception.NotConnectedException; @@ -43,7 +46,8 @@ public class CacheCarDAO extends AbstractCacheDAO implements CarDAO { public static final String CAR_CACHE_FILE_NAME = "cache_cars"; @Inject - public CacheCarDAO(){} + public CacheCarDAO() { + } @Override public List getAllCars() throws NotConnectedException, DataRetrievalFailureException { @@ -56,7 +60,19 @@ public Observable> getAllCarsObservable() { } @Override - public String createCar(Car car) throws NotConnectedException, DataCreationFailureException, UnauthorizedException { + public List getCarsByUser(User user) throws UserNotAuthenticatedException, + NotConnectedException, DataRetrievalFailureException, UnauthorizedException { + return null; + } + + @Override + public Observable> getCarsByUserObservable(User user) { + return null; + } + + @Override + public String createCar(Car car) throws NotConnectedException, DataCreationFailureException, + UnauthorizedException { return null; } diff --git a/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/CacheTrackDAO.java b/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/CacheTrackDAO.java index 203220a2e..a409a8c10 100644 --- a/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/CacheTrackDAO.java +++ b/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/CacheTrackDAO.java @@ -93,20 +93,19 @@ public Integer getTotalTrackCount() throws DataRetrievalFailureException, } @Override - public String createTrack(Track track) throws DataCreationFailureException, + public Track createTrack(Track track) throws DataCreationFailureException, NotConnectedException, ResourceConflictException, UnauthorizedException { throw new NotConnectedException("Not implemented for Cache DAO"); } @Override - public void deleteTrack(String remoteID) throws DataUpdateFailureException, - NotConnectedException, UnauthorizedException { - throw new NotConnectedException("Not implemented for Cache DAO"); + public Observable createTrackObservable(Track track) { + return Observable.error(new NotConnectedException("Not implemented for Cache DAO")); } @Override public void deleteTrack(Track track) throws DataUpdateFailureException, NotConnectedException, UnauthorizedException { - + throw new NotConnectedException("Not implemented for Cache DAO"); } } diff --git a/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/RemoteCarDAO.java b/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/RemoteCarDAO.java index df4cb8ac3..6da1dc6e6 100644 --- a/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/RemoteCarDAO.java +++ b/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/RemoteCarDAO.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -20,6 +20,7 @@ import org.envirocar.core.dao.CarDAO; import org.envirocar.core.entity.Car; +import org.envirocar.core.entity.User; import org.envirocar.core.exception.DataCreationFailureException; import org.envirocar.core.exception.DataRetrievalFailureException; import org.envirocar.core.exception.NotConnectedException; @@ -71,7 +72,6 @@ public List getAllCars() throws NotConnectedException, DataRetrievalFailure * @throws DataRetrievalFailureException */ private List getAllCars(int page) throws DataRetrievalFailureException { - final CarService carService = EnviroCarService.getCarService(); Call> carsCall = remoteService.getAllCars(page); try { @@ -98,6 +98,39 @@ public Observable> getAllCarsObservable() { return getAllCarsObservable(1); } + @Override + public List getCarsByUser(User user) throws UnauthorizedException, + NotConnectedException, DataRetrievalFailureException { + LOG.info(String.format("getCarsByUser(%s)", user.getUsername())); + Call> carsCall = remoteService.getAllCars(user.getUsername()); + try{ + Response> carsResponse = executeCall(carsCall); + return carsResponse.body(); + } catch (IOException | ResourceConflictException e) { + throw new DataRetrievalFailureException(e); + } + } + + @Override + public Observable> getCarsByUserObservable(User user) { + LOG.info(String.format("getCarsByUserObservable(%s)", user.getUsername())); + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(Subscriber> subscriber) { + LOG.info("call:"); + try { + subscriber.onStart(); + subscriber.onNext(getCarsByUser(user)); + } catch (UnauthorizedException | + NotConnectedException | + DataRetrievalFailureException e){ + subscriber.onError(e); + } + subscriber.onCompleted(); + } + }); + } + @Override public String createCar(Car car) throws NotConnectedException, DataCreationFailureException, UnauthorizedException { @@ -159,11 +192,9 @@ private Observable> getAllCarsObservable(final int page) { .concatMap(new Func1>, Observable>>() { @Override public Observable> call(Call> listCall) { - boolean hasNextPage = false; - Response> response = null; try { // Execute the call. - response = listCall.execute(); + Response> response = listCall.execute(); Observable> res = Observable.just(response.body()); // Search for "rel=last". If this exists, then this was not the last diff --git a/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/RemoteTrackDAO.java b/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/RemoteTrackDAO.java index 20c87f58d..479f26ebd 100644 --- a/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/RemoteTrackDAO.java +++ b/org.envirocar.remote/src/main/java/org/envirocar/remote/dao/RemoteTrackDAO.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -55,14 +55,6 @@ public class RemoteTrackDAO extends BaseRemoteDAO implements TrackDAO { private static final Logger LOG = Logger.getLogger(RemoteTrackDAO.class); - - /** - * Constructor. - */ - public RemoteTrackDAO() { - super(null, null); - } - /** * Constructor. * @@ -84,16 +76,7 @@ public Track getTrackById(String id) throws DataRetrievalFailureException, try { // Execute the request call - Response trackResponse = trackCall.execute(); - - // If the request call was not successful, then assert the status code and throw an - // exceptiom - if (!trackResponse.isSuccess()) { - LOG.warn(String.format("getTrack was not successful for the following reason: %s", - trackResponse.message())); - EnvirocarServiceUtils.assertStatusCode( - trackResponse.code(), trackResponse.message()); - } + Response trackResponse = executeCall(trackCall); // If it was successful, then return the track. LOG.debug("getTrack() was successful"); @@ -185,7 +168,7 @@ public Integer getTotalTrackCount() throws NotConnectedException, } @Override - public String createTrack(Track track) throws DataCreationFailureException, + public Track createTrack(Track track) throws DataCreationFailureException, NotConnectedException, ResourceConflictException, UnauthorizedException { LOG.info("createTrack()"); @@ -212,8 +195,10 @@ public String createTrack(Track track) throws DataCreationFailureException, String location = EnvirocarServiceUtils.resolveRemoteLocation(uploadTrackResponse); LOG.info("Uploaded remote location: " + location); - // Return the location; - return location.substring(location.lastIndexOf('/') + 1, location.length()); + // Set the remoteID ... + track.setRemoteID(location.substring(location.lastIndexOf('/') + 1, location.length())); + // ... and return the track; + return track; } catch (IOException e) { throw new DataCreationFailureException(e); } catch (ResourceConflictException e) { @@ -221,6 +206,27 @@ public String createTrack(Track track) throws DataCreationFailureException, } } + @Override + public Observable createTrackObservable(Track track) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + LOG.info("call: creating remote track."); + subscriber.onStart(); + try { + subscriber.onNext(createTrack(track)); + } catch (DataCreationFailureException | + NotConnectedException | + ResourceConflictException | + UnauthorizedException e) { + LOG.error(e.getMessage(), e); + subscriber.onError(e); + } + subscriber.onCompleted(); + } + }); + } + @Override public List getTrackIds() throws NotConnectedException, UnauthorizedException { return getTrackIds(100); @@ -292,8 +298,12 @@ public void call(Subscriber> subscriber) { } @Override - public void deleteTrack(String remoteID) throws DataUpdateFailureException, + public void deleteTrack(Track track) throws DataUpdateFailureException, NotConnectedException, UnauthorizedException { + Preconditions.checkState(track.getRemoteID() != null, "No RemoteID for this Track."); + Preconditions.checkState(track.isRemoteTrack(), "Track is not a remote track. Track " + + "cannot be deleted"); + String remoteID = track.getRemoteID(); LOG.info(String.format("deleteRemoteTrack(%s)", remoteID)); // If not logged in, then throw an exception @@ -326,11 +336,4 @@ public void deleteTrack(String remoteID) throws DataUpdateFailureException, } } - @Override - public void deleteTrack(Track track) throws DataUpdateFailureException, - NotConnectedException, UnauthorizedException { - Preconditions.checkState(track.getRemoteID() != null, "No RemoteID for this Track."); - deleteTrack(track.getRemoteID()); - } - } diff --git a/org.envirocar.remote/src/main/java/org/envirocar/remote/serializer/TrackSerializer.java b/org.envirocar.remote/src/main/java/org/envirocar/remote/serializer/TrackSerializer.java index 2175cd95c..06cc154b4 100644 --- a/org.envirocar.remote/src/main/java/org/envirocar/remote/serializer/TrackSerializer.java +++ b/org.envirocar.remote/src/main/java/org/envirocar/remote/serializer/TrackSerializer.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -134,8 +134,7 @@ public JsonElement serialize(Track src, Type typeOfSrc, JsonSerializationContext trackProperties.addProperty(names.get(i).toString(), json.getString(names.get(i).toString())); } - } - else { + } else { LOG.warn("The track does not provide metadata!"); } } catch (JSONException e) { @@ -153,7 +152,7 @@ public JsonElement serialize(Track src, Type typeOfSrc, JsonSerializationContext try { for (Measurement measurement : measurements) { JsonElement measurementJson = createMeasurementProperties( - measurement, src.getCar().getId()); + measurement, src.getCar()); trackFeatures.add(measurementJson); } } catch (JSONException e) { @@ -169,7 +168,7 @@ public JsonElement serialize(Track src, Type typeOfSrc, JsonSerializationContext return result; } - private JsonElement createMeasurementProperties(Measurement src, String sensorId) throws + private JsonElement createMeasurementProperties(Measurement src, Car car) throws JSONException { // Create the Geometry json object JsonObject geometryJsonObject = new JsonObject(); @@ -186,10 +185,10 @@ private JsonElement createMeasurementProperties(Measurement src, String sensorId JsonObject propertiesJson = new JsonObject(); propertiesJson.addProperty(Track.KEY_TRACK_FEATURES_PROPERTIES_TIME, Util.longToIsoDate(src.getTime())); - propertiesJson.addProperty("sensor", sensorId); + propertiesJson.addProperty("sensor", car.getId()); // Add all measured phenomenons to this measurement. - JsonObject phenomenons = createPhenomenons(src); + JsonObject phenomenons = createPhenomenons(src, car.getFuelType() == Car.FuelType.DIESEL); if (phenomenons != null) { propertiesJson.add(Track.KEY_TRACK_FEATURES_PROPERTIES_PHENOMENONS, phenomenons); } @@ -243,6 +242,7 @@ public Track deserialize(JsonElement json, Type typeOfT, JsonDeserializationCont // Create the track Track track = new TrackImpl(Track.DownloadState.DOWNLOADED); + track.setTrackStatus(Track.TrackStatus.FINISHED); track.setRemoteID(id); track.setName(name); track.setDescription(description); @@ -252,7 +252,8 @@ public Track deserialize(JsonElement json, Type typeOfT, JsonDeserializationCont return track; } - private JsonObject createPhenomenons(Measurement measurement) throws JSONException { + private JsonObject createPhenomenons(Measurement measurement, boolean isDiesel) throws + JSONException { if (measurement.getAllProperties().isEmpty()) { return null; } @@ -261,7 +262,11 @@ private JsonObject createPhenomenons(Measurement measurement) throws JSONExcepti Map props = measurement.getAllProperties(); for (Measurement.PropertyKey key : props.keySet()) { if (supportedPhenomenons.contains(key)) { - result.add(key.toString(), createValue(props.get(key))); + if (isDiesel && (key == Measurement.PropertyKey.CO2 || key == Measurement.PropertyKey.CONSUMPTION) ){ + // DO NOTHING TODO delete when necessary + } else { + result.add(key.toString(), createValue(props.get(key))); + } } } return result; diff --git a/org.envirocar.remote/src/main/java/org/envirocar/remote/service/CarService.java b/org.envirocar.remote/src/main/java/org/envirocar/remote/service/CarService.java index 69aba94f2..df0120d6b 100644 --- a/org.envirocar.remote/src/main/java/org/envirocar/remote/service/CarService.java +++ b/org.envirocar.remote/src/main/java/org/envirocar/remote/service/CarService.java @@ -27,6 +27,7 @@ import retrofit.http.Body; import retrofit.http.GET; import retrofit.http.POST; +import retrofit.http.Path; import retrofit.http.Query; import rx.Observable; @@ -39,14 +40,17 @@ public interface CarService { Call> getAllCars(); @GET("sensors/") - Call> getAllCars(@Query("page") int page); + Observable> getAllCarsObservable(); @GET("sensors/") - Observable> getAllCarsObservable(); + Call> getAllCars(@Query("page") int page); @GET("sensors/") Observable> getAllCarsObservable(@Query("page") int page); + @GET("users/{user}/sensors/") + Call> getAllCars(@Path("user") String user); + @POST("sensors") Call createCar(@Body Car car); -} \ No newline at end of file +} diff --git a/org.envirocar.remote/src/main/java/org/envirocar/remote/util/EnvirocarServiceUtils.java b/org.envirocar.remote/src/main/java/org/envirocar/remote/util/EnvirocarServiceUtils.java index b0a972ce4..c9b8dbc49 100644 --- a/org.envirocar.remote/src/main/java/org/envirocar/remote/util/EnvirocarServiceUtils.java +++ b/org.envirocar.remote/src/main/java/org/envirocar/remote/util/EnvirocarServiceUtils.java @@ -198,8 +198,8 @@ public static final int resolvePageCount(Response response) { } } - // If the body is null, then return 0. Otherwise, return 1. - return response.body() != null ? 1 : 0; + // If the body is null, then return 0. Otherwise, return 1. // TODO + return response.body() != null ? 0 : 0; } /** diff --git a/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDBModule.java b/org.envirocar.storage/src/main/java/org/envirocar/storage/DatabaseModule.java similarity index 94% rename from org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDBModule.java rename to org.envirocar.storage/src/main/java/org/envirocar/storage/DatabaseModule.java index c334edb84..eaaa33e33 100644 --- a/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDBModule.java +++ b/org.envirocar.storage/src/main/java/org/envirocar/storage/DatabaseModule.java @@ -1,73 +1,73 @@ -/** - * Copyright (C) 2013 - 2015 the enviroCar community - * - * This file is part of the enviroCar app. - * - * The enviroCar app is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The enviroCar app is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with the enviroCar app. If not, see http://www.gnu.org/licenses/. - */ -package org.envirocar.storage; - -import android.content.Context; -import android.database.sqlite.SQLiteOpenHelper; - -import com.squareup.sqlbrite.BriteDatabase; -import com.squareup.sqlbrite.SqlBrite; - -import org.envirocar.core.injection.InjectApplicationScope; - -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; - -/** - * TODO JavaDoc - * - * @author dewall - */ -@Module( - complete = false, - library = true, - injects = { - EnviroCarDBImpl.class, - EnviroCarDBOpenHelper.class - } -) -public final class EnviroCarDBModule { - - @Provides - @Singleton - SQLiteOpenHelper provideSQLiteOpenHelper(@InjectApplicationScope Context context) { - return new EnviroCarDBOpenHelper(context); - } - - @Provides - @Singleton - SqlBrite provideSqlBrite() { - return SqlBrite.create(); - } - - @Provides - @Singleton - BriteDatabase provideBriteDatabase(SqlBrite sqlBrite, SQLiteOpenHelper helper) { - return sqlBrite.wrapDatabaseHelper(helper); - } - - @Provides - @Singleton - EnviroCarDB provideEnvirocarDB(BriteDatabase briteDatabase) { - return new EnviroCarDBImpl(briteDatabase); - } - -} +/** + * Copyright (C) 2013 - 2015 the enviroCar community + * + * This file is part of the enviroCar app. + * + * The enviroCar app is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The enviroCar app is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the enviroCar app. If not, see http://www.gnu.org/licenses/. + */ +package org.envirocar.storage; + +import android.content.Context; +import android.database.sqlite.SQLiteOpenHelper; + +import com.squareup.sqlbrite.BriteDatabase; +import com.squareup.sqlbrite.SqlBrite; + +import org.envirocar.core.injection.InjectApplicationScope; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * TODO JavaDoc + * + * @author dewall + */ +@Module( + complete = false, + library = true, + injects = { + EnviroCarDBImpl.class, + EnviroCarDBOpenHelper.class + } +) +public final class DatabaseModule { + + @Provides + @Singleton + SQLiteOpenHelper provideSQLiteOpenHelper(@InjectApplicationScope Context context) { + return new EnviroCarDBOpenHelper(context); + } + + @Provides + @Singleton + SqlBrite provideSqlBrite() { + return SqlBrite.create(); + } + + @Provides + @Singleton + BriteDatabase provideBriteDatabase(SqlBrite sqlBrite, SQLiteOpenHelper helper) { + return sqlBrite.wrapDatabaseHelper(helper); + } + + @Provides + @Singleton + EnviroCarDB provideEnvirocarDB(BriteDatabase briteDatabase) { + return new EnviroCarDBImpl(briteDatabase); + } + +} diff --git a/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDB.java b/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDB.java index ff6a0d00a..8268c545c 100644 --- a/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDB.java +++ b/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDB.java @@ -22,6 +22,7 @@ import org.envirocar.core.entity.Track; import org.envirocar.core.exception.MeasurementSerializationException; import org.envirocar.core.exception.TrackSerializationException; +import org.envirocar.core.util.TrackMetadata; import java.util.List; @@ -53,6 +54,8 @@ public interface EnviroCarDB { */ Observable> getAllTracks(boolean lazy); + Observable> getAllTracksByCar(String id, boolean lazy); + Observable> getAllLocalTracks(); Observable> getAllLocalTracks(boolean lazy); @@ -65,13 +68,19 @@ public interface EnviroCarDB { void insertTrack(Track track) throws TrackSerializationException; - Observable insertTrackObservable(Track track); + Observable insertTrackObservable(Track track); + + boolean updateTrack(Track track); + + Observable updateTrackObservable(Track track); + + boolean updateCarIdOfTracks(String currentId, String newId); void deleteTrack(Track.TrackId trackId); void deleteTrack(Track track); - Observable deleteTrackObservable(Track track); + Observable deleteTrackObservable(Track track); Observable deleteAllRemoteTracks(); @@ -87,5 +96,11 @@ public interface EnviroCarDB { Observable fetchTrack(Observable track, final boolean lazy); + Observable getActiveTrackObservable(boolean lazy); + + void updateTrackMetadata(final Track track, final TrackMetadata trackMetadata) throws + TrackSerializationException; + Observable updateTrackMetadataObservable(final Track track, final TrackMetadata trackMetadata) throws + TrackSerializationException; } diff --git a/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDBImpl.java b/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDBImpl.java index f6257324e..79275d554 100644 --- a/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDBImpl.java +++ b/org.envirocar.storage/src/main/java/org/envirocar/storage/EnviroCarDBImpl.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -84,6 +84,12 @@ public Observable> getAllTracks(final boolean lazy) { "SELECT * FROM " + TrackTable.TABLE_TRACK, lazy); } + @Override + public Observable> getAllTracksByCar(String carID, boolean lazy) { + return fetchTracksObservable("SELECT * FROM " + TrackTable.TABLE_TRACK + + " WHERE " + TrackTable.KEY_TRACK_CAR_ID + "='" + carID + "'", lazy); + } + @Override public Observable> getAllLocalTracks() { return getAllLocalTracks(false); @@ -120,12 +126,15 @@ public void call(Subscriber subscriber) { } public void insertTrack(final Track track) throws TrackSerializationException { + LOG.info("insertTrack(): trying to insert a new track"); BriteDatabase.Transaction transaction = briteDatabase.newTransaction(); try { - long result = briteDatabase.insert(TrackTable.TABLE_TRACK, TrackTable.toContentValues - (track)); + long result = briteDatabase.insert(TrackTable.TABLE_TRACK, + TrackTable.toContentValues(track)); Track.TrackId trackId = new Track.TrackId(result); track.setTrackID(trackId); + LOG.info(String.format("insertTrack(): " + + "track has been successfully inserted ->[id = %s]", "" + result)); if (track.getMeasurements().size() > 0) { for (Measurement measurement : track.getMeasurements()) { @@ -145,12 +154,13 @@ public void insertTrack(final Track track) throws TrackSerializationException { } @Override - public Observable insertTrackObservable(final Track track) { - return Observable.create(new Observable.OnSubscribe() { + public Observable insertTrackObservable(final Track track) { + return Observable.create(new Observable.OnSubscribe() { @Override - public void call(Subscriber subscriber) { + public void call(Subscriber subscriber) { try { insertTrack(track); + subscriber.onNext(track); } catch (TrackSerializationException e) { subscriber.onError(e); } @@ -159,6 +169,37 @@ public void call(Subscriber subscriber) { }); } + @Override + public boolean updateTrack(Track track) { + LOG.info(String.format("updateTrack(%s)", track.getTrackID())); + ContentValues trackValues = TrackTable.toContentValues(track); + int update = briteDatabase.update(TrackTable.TABLE_TRACK, trackValues, + TrackTable.KEY_TRACK_ID + "=" + track.getTrackID()); + return update != -1; + } + + @Override + public Observable updateTrackObservable(Track track) { + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + subscriber.onStart(); + if (updateTrack(track)) + subscriber.onNext(track); + subscriber.onCompleted(); + } + }); + } + + @Override + public boolean updateCarIdOfTracks(String currentId, String newId) { + ContentValues values = new ContentValues(); + values.put(TrackTable.KEY_TRACK_CAR_ID, newId); + briteDatabase.update(TrackTable.TABLE_TRACK, values, + TrackTable.KEY_TRACK_CAR_ID + "=?", new String[]{currentId}); + return true; + } + @Override public void deleteTrack(Track.TrackId trackId) { briteDatabase.delete(TrackTable.TABLE_TRACK, @@ -172,11 +213,12 @@ public void deleteTrack(Track track) { } @Override - public Observable deleteTrackObservable(Track track) { - return Observable.create(new Observable.OnSubscribe() { + public Observable deleteTrackObservable(Track track) { + return Observable.create(new Observable.OnSubscribe() { @Override - public void call(Subscriber subscriber) { + public void call(Subscriber subscriber) { deleteTrack(track); + subscriber.onNext(track); subscriber.onCompleted(); } }); @@ -200,6 +242,7 @@ public Observable deleteAllRemoteTracks() { @Override public void insertMeasurement(final Measurement measurement) throws MeasurementSerializationException { + LOG.info("inserted measurement into track " + measurement.getTrackId()); briteDatabase.insert(MeasurementTable.TABLE_NAME, MeasurementTable.toContentValues(measurement)); } @@ -258,11 +301,11 @@ public void updateTrackMetadata(final Track track, final TrackMetadata trackMeta } } - public Observable updateTrackMetadataObservable(final Track track, final TrackMetadata - trackMetadata) { - return Observable.create(new Observable.OnSubscribe() { + public Observable updateTrackMetadataObservable( + final Track track, final TrackMetadata trackMetadata) { + return Observable.create(new Observable.OnSubscribe() { @Override - public void call(Subscriber subscriber) { + public void call(Subscriber subscriber) { try { updateTrackMetadata(track, trackMetadata); } catch (TrackSerializationException e) { @@ -275,10 +318,6 @@ public void call(Subscriber subscriber) { }); } - private Track getActiveTrackReference() { - return null; - } - @Override public Observable fetchTracks( Observable> tracks, final boolean lazy) { @@ -292,8 +331,8 @@ public Observable call(List tracks) { } @Override - public Observable fetchTrack(Observable track, final boolean lazy) { - return track + public Observable fetchTrack(Observable trackObservable, final boolean lazy) { + return trackObservable .flatMap(new Func1>() { @Override public Observable call(Track track) { @@ -302,6 +341,16 @@ public Observable call(Track track) { }); } + @Override + public Observable getActiveTrackObservable(boolean lazy) { + return fetchTrackObservable( + "SELECT * FROM " + TrackTable.TABLE_TRACK + + " WHERE " + TrackTable.KEY_TRACK_STATE + "='" + + Track.TrackStatus.ONGOING + "'" + + " ORDER BY " + TrackTable.KEY_TRACK_ID + " DESC" + + " LIMIT 1", lazy); + } + private void deleteMeasurementsOfTrack(Track.TrackId trackId) { BriteDatabase.Transaction transaction = briteDatabase.newTransaction(); try { @@ -313,10 +362,6 @@ private void deleteMeasurementsOfTrack(Track.TrackId trackId) { } } - private void deleteMeasurementsOfTrack(Track track) { - deleteMeasurementsOfTrack(track.getTrackID()); - } - private Observable fetchMeasurements(final Track track) { return briteDatabase.createQuery( MeasurementTable.TABLE_NAME, @@ -355,43 +400,39 @@ public Track call(Measurement measurement) { } private Observable fetchTrackObservable(String sql, boolean lazy) { - return fetchTrackObservable(briteDatabase + return briteDatabase .createQuery(TrackTable.TABLE_TRACK, sql) - .mapToOne(TrackTable.MAPPER) - .take(1), lazy); + .mapToOneOrDefault(TrackTable.MAPPER, null) + .take(1) + .compose(fetchTrackObservable(lazy)); } - private Observable fetchTrackObservable( - Observable track, final boolean lazy) { - return track.map(new Func1() { - @Override - public Track call(Track track) { - return lazy ? fetchStartEndTimeSilent(track) : - fetchMeasurementsSilent(track); - } + private Observable.Transformer fetchTrackObservable(final boolean lazy) { + return trackObservable -> trackObservable.map(track -> { + if (track == null) + return null; + + // return the track either leither or completly fetched. + return lazy ? fetchStartEndTimeSilent(track) : fetchMeasurementsSilent(track); }); } private Observable> fetchTracksObservable(String sql, boolean lazy) { - return fetchTracksObservable( - briteDatabase.createQuery(TrackTable.TABLE_TRACK, sql) - .mapToList(TrackTable.MAPPER), lazy); - } - - private Observable> fetchTracksObservable( - Observable> tracks, boolean lazy) { - return tracks.map(new Func1, List>() { - @Override - public List call(List tracks) { - for (Track track : tracks) { - if (lazy) { - fetchStartEndTimeSilent(track); - } else { - fetchMeasurementsSilent(track); - } + return briteDatabase.createQuery(TrackTable.TABLE_TRACK, sql) + .mapToList(TrackTable.MAPPER) + .compose(fetchTracks(lazy)); + } + + private Observable.Transformer, List> fetchTracks(boolean lazy) { + return trackObservable -> trackObservable.map(tracks -> { + for (Track track : tracks) { + if (lazy) { + fetchStartEndTimeSilent(track); + } else { + fetchMeasurementsSilent(track); } - return tracks; } + return tracks; }); } @@ -412,9 +453,12 @@ private Track fetchStartEndTimeSilent(final Track track) { " WHERE " + MeasurementTable.KEY_TRACK + "=\"" + track.getTrackID() + "\"" + " ORDER BY " + MeasurementTable.KEY_TIME + " ASC LIMIT 1"); - track.setStartTime( - startTime.getLong( - startTime.getColumnIndex(MeasurementTable.KEY_TIME))); + + if (startTime.moveToFirst()) { + track.setStartTime( + startTime.getLong( + startTime.getColumnIndex(MeasurementTable.KEY_TIME))); + } Cursor endTime = briteDatabase.query( "SELECT " + MeasurementTable.KEY_TIME + @@ -422,19 +466,14 @@ private Track fetchStartEndTimeSilent(final Track track) { " WHERE " + MeasurementTable.KEY_TRACK + "=\"" + track.getTrackID() + "\"" + " ORDER BY " + MeasurementTable.KEY_TIME + " DESC LIMIT 1"); - track.setEndTime( - endTime.getLong( - startTime.getColumnIndex(MeasurementTable.KEY_TIME))); + + if (endTime.moveToFirst()) { + track.setEndTime( + endTime.getLong( + startTime.getColumnIndex(MeasurementTable.KEY_TIME))); + } return track; } - // @Override - // public Observable getAllLocalTracks(boolean lazy) { - // return fetchTracks(briteDatabase.createQuery( - // TrackTable.TABLE_TRACK, - // "SELECT * FROM " + TrackTable.TABLE_TRACK + - // " WHERE " + TrackTable.KEY_REMOTE_ID + " IS NULL") - // .mapToList(TrackTable.MAPPER), lazy); - // } } diff --git a/org.envirocar.storage/src/main/java/org/envirocar/storage/dao/LocalTrackDAO.java b/org.envirocar.storage/src/main/java/org/envirocar/storage/dao/LocalTrackDAO.java index 6aeae2023..86fa18d9b 100644 --- a/org.envirocar.storage/src/main/java/org/envirocar/storage/dao/LocalTrackDAO.java +++ b/org.envirocar.storage/src/main/java/org/envirocar/storage/dao/LocalTrackDAO.java @@ -1,18 +1,18 @@ /** * Copyright (C) 2013 - 2015 the enviroCar community - * + *

* This file is part of the enviroCar app. - * + *

* The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. - * + *

* You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ @@ -30,6 +30,8 @@ import java.util.List; +import javax.inject.Inject; + import rx.Observable; /** @@ -39,8 +41,17 @@ */ public class LocalTrackDAO implements TrackDAO { -// @Inject - protected EnviroCarDB database; + protected final EnviroCarDB database; + + /** + * Injectable constructor. + * + * @param database the database instance to be injected. + */ + @Inject + public LocalTrackDAO(EnviroCarDB database) { + this.database = database; + } @Override public Track getTrackById(String id) throws DataRetrievalFailureException, @@ -92,20 +103,19 @@ public Integer getTotalTrackCount() throws DataRetrievalFailureException, } @Override - public String createTrack(Track track) throws DataCreationFailureException, + public Track createTrack(Track track) throws DataCreationFailureException, NotConnectedException, ResourceConflictException, UnauthorizedException { return null; } @Override - public void deleteTrack(String remoteID) throws DataUpdateFailureException, - NotConnectedException, UnauthorizedException { - + public Observable createTrackObservable(Track track) { + return database.insertTrackObservable(track); } @Override public void deleteTrack(Track track) throws DataUpdateFailureException, NotConnectedException, UnauthorizedException { - + database.deleteTrack(track); } } diff --git a/settings.gradle b/settings.gradle index 3cc493ebc..a5a11c8b3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include ':org.envirocar.app', ':org.envirocar.core', ':org.envirocar.remote', ':org.envirocar.obd', ':org.envirocar.storage' +include ':org.envirocar.app', ':org.envirocar.core', ':org.envirocar.remote', ':org.envirocar.obd', ':org.envirocar.storage', ':org.envirocar.algorithm' include ':android-obd-simulator'