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 super Measurement> 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 extends Timestamped> 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 super Track> 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 super Track> 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 super Boolean> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super List> 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 super List> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
"); - 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(":
* 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 super List> 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 super List> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
* 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 super List> 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 super List> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
* 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 - * + *
* 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
* 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
* 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
+ * 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 super Track> 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 super Track> 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 super Track> 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 super Track> 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
+ * 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 super Track> 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 super Track> 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 super Track> 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
* 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 super BluetoothSocketWrapper> 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 super UUID> 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 super R> call(Subscriber super R> 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
* 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 super UUID> 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 super R> call(Subscriber super R> 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
* 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 - * + *
* 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 super R> call(Subscriber super R> 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
true
* 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 super R> call(Subscriber super R> 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
* 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 super R> call(Subscriber super R> 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
* 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 super R> call(Subscriber super R> 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
* 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 super R> call(Subscriber super R> 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
* 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 - * + *
* 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 super R> call(Subscriber super R> 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
* 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 super R> call(Subscriber super R> 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
* 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 super R> call(Subscriber super R> 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