├── settings.gradle ├── .gitignore ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── checkstyle.gradle ├── javadoc.gradle ├── mavenize.gradle └── bintray-upload.gradle ├── android-net ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ └── layout │ │ │ │ └── activity_vinli_sign_in.xml │ │ ├── java │ │ │ └── li │ │ │ │ └── vin │ │ │ │ └── net │ │ │ │ ├── VinliItem.java │ │ │ │ ├── Users.java │ │ │ │ ├── DistanceUnit.java │ │ │ │ ├── Devices.java │ │ │ │ ├── ObjectRef.java │ │ │ │ ├── VinliError.java │ │ │ │ ├── Vehicles.java │ │ │ │ ├── DataItem.java │ │ │ │ ├── AutoParcelAdapter.java │ │ │ │ ├── Locations.java │ │ │ │ ├── Diagnostics.java │ │ │ │ ├── Trips.java │ │ │ │ ├── Snapshots.java │ │ │ │ ├── Messages.java │ │ │ │ ├── Collisions.java │ │ │ │ ├── Notifications.java │ │ │ │ ├── Dummies.java │ │ │ │ ├── VinliBaseActivity.java │ │ │ │ ├── User.java │ │ │ │ ├── Events.java │ │ │ │ ├── Rules.java │ │ │ │ ├── ReportCards.java │ │ │ │ ├── VinliSignInHandler.java │ │ │ │ ├── BatteryStatus.java │ │ │ │ ├── VinliEndpoint.java │ │ │ │ ├── utils │ │ │ │ ├── RxAdapter.java │ │ │ │ └── PageAdapter.java │ │ │ │ ├── Subscriptions.java │ │ │ │ ├── Wrapped.java │ │ │ │ ├── Distances.java │ │ │ │ ├── ObserverManager.java │ │ │ │ ├── DistanceList.java │ │ │ │ ├── Coordinate.java │ │ │ │ ├── Dtc.java │ │ │ │ ├── Duktaper.java │ │ │ │ ├── Collision.java │ │ │ │ ├── Event.java │ │ │ │ ├── BearingCalculator.java │ │ │ │ ├── Vinli.java │ │ │ │ ├── Notification.java │ │ │ │ ├── ReportCard.java │ │ │ │ ├── Odometer.java │ │ │ │ ├── SignInActivity.java │ │ │ │ ├── Dummy.java │ │ │ │ ├── SupportedPids.java │ │ │ │ ├── Location.java │ │ │ │ ├── OdometerTrigger.java │ │ │ │ └── TimeSeries.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── li │ │ │ └── vin │ │ │ └── net │ │ │ ├── UsersIntegrationTests.java │ │ │ ├── DevicesIntegrationTests.java │ │ │ ├── DiagnosticsIntegrationTests.java │ │ │ ├── CollisionsIntegrationTests.java │ │ │ ├── VehiclesIntegrationTests.java │ │ │ └── NotificationsIntegrationTests.java │ └── androidTest │ │ └── java │ │ └── li │ │ └── vin │ │ └── net │ │ ├── InstTestHelper.java │ │ └── StreamingInstTest.java ├── integration.properties ├── proguard-rules.pro └── build.gradle ├── .travis.yml ├── README.md ├── deploy_website.sh ├── gradle.properties ├── LICENSE.txt ├── gradlew.bat ├── gradlew ├── checkstyle.xml └── circle.yml /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':android-net' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | 4 | .gradle 5 | local.properties 6 | build 7 | 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinli/android-net/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android-net/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Vinli Sign In 3 | 4 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/VinliItem.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | 5 | public interface VinliItem extends Parcelable { 6 | 7 | String id(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Users.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import retrofit2.http.GET; 4 | import rx.Observable; 5 | 6 | /*package*/ interface Users { 7 | 8 | @GET("users/_current") 9 | Observable> currentUser(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 14 12:00:20 CDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /android-net/src/main/res/layout/activity_vinli_sign_in.xml: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | android: 3 | components: 4 | # Uncomment the lines below if you want to 5 | # use the latest revision of Android SDK Tools 6 | - platform-tools 7 | - tools 8 | 9 | # The BuildTools version used by your project 10 | - build-tools-22.0.1 11 | 12 | # The SDK version used to compile your project 13 | - android-22 14 | 15 | # Additional components 16 | # - extra-google-google_play_services 17 | # - extra-google-m2repository 18 | - extra-android-m2repository 19 | 20 | before_script: 21 | - chmod +x gradlew 22 | 23 | script: "./gradlew build" 24 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/DistanceUnit.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | public enum DistanceUnit { 4 | KILOMETERS("km"), 5 | METERS("m"), 6 | MILES("mi"); 7 | 8 | private String unitStr; 9 | 10 | private DistanceUnit(String unit){ 11 | this.unitStr = unit; 12 | } 13 | 14 | /*package*/ String getDistanceUnitStr(){ 15 | return this.unitStr; 16 | } 17 | 18 | /*package*/ static DistanceUnit parse(String str) { 19 | if ("km".equals(str)) return KILOMETERS; 20 | if ("m".equals(str)) return METERS; 21 | if ("mi".equals(str)) return MILES; 22 | return null; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /android-net/integration.properties: -------------------------------------------------------------------------------- 1 | ACCESS_TOKEN=DEFAULT_ACCESS_TOKEN 2 | VEHICULARIZATION_ACCESS_TOKEN=DEFAULT_ACCESS_TOKEN 3 | DEVICE_ID=DEFAULT_DEVICE_ID 4 | SUBSCRIPTION_ID=DEFAULT_SUBSCRIPTION_ID 5 | RULE_ID=DEFAULT_RULE_ID 6 | VEHICLE_RULE_ID=DEFAULT_RULE_ID 7 | VEHICLE_ID=DEFAULT_VEHICLE_ID 8 | SECOND_VEHICLE_ID=DEFAULT_VEHICLE_ID 9 | EVENT_ID=DEFAULT_EVENT_ID 10 | NOTIFICATION_ID=DEFAULT_NOTIFICATION_ID 11 | MESSAGE_ID=DEFAULT_MESSAGE_ID 12 | TRIP_ID=DEFAULT_TRIP_ID 13 | DUMMY_ID=DEFAULT_DUMMY_ID 14 | ROUTE_ID=DEFAULT_ROUTE_ID 15 | VIN=DEFAULT_VIN 16 | ODO_ID=DEFAULT_ODO_ID 17 | ODO_TRIGGER_ID=DEFAULT_ODO_TRIGGER_ID 18 | REPORT_CARD_ID=DEFAULT_REPORT_CARD_ID 19 | COLLISION_ID=DEFAULT_COLLISION_ID -------------------------------------------------------------------------------- /android-net/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/kyle/dev/android/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Devices.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | import retrofit2.http.Query; 9 | import retrofit2.http.Url; 10 | import rx.Observable; 11 | 12 | /*package*/ interface Devices { 13 | 14 | @GET("devices") 15 | Observable> devices( 16 | @Nullable @Query("limit") Integer limit, 17 | @Nullable @Query("offset") Integer offset); 18 | 19 | @GET("devices/{deviceId}") 20 | Observable> device( 21 | @NonNull @Path("deviceId") String deviceId); 22 | 23 | @GET Observable> devicesForUrl(@NonNull @Url String url); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /android-net/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Vinli Android Net SDK 2 | ===================== 3 | 4 | An Android client for interacting with the Vinli backend from within your application. [Sample App](https://github.com/vinli/android-net-demo) 5 | 6 | Download 7 | -------- 8 | 9 | You can also depend on this library through Gradle from jcenter: 10 | ```groovy 11 | compile 'li.vin:android-net:1.0.17' 12 | ``` 13 | 14 | Conventions 15 | ----------- 16 | ### [RxJava](https://github.com/ReactiveX/RxJava/wiki) 17 | The developer interfaces with the SDK using reactive Observables and Subscriptions. 18 | All data from the device is streamed via Observable, and the data stream is stopped by unsubscribing from the subscription. 19 | All models are immutable. 20 | 21 | Docs 22 | ---- 23 | 24 | ### [JavaDocs](http://vinli.github.io/android-net/) 25 | -------------------------------------------------------------------------------- /gradle/checkstyle.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'checkstyle' 2 | 3 | checkstyle { 4 | configFile rootProject.file('checkstyle.xml') 5 | showViolations true 6 | toolVersion = '6.6' 7 | ignoreFailures = true 8 | } 9 | 10 | def variants = android.hasProperty('applicationVariants') \ 11 | ? android.applicationVariants \ 12 | : android.libraryVariants 13 | 14 | variants.all { variant -> 15 | def name = variant.buildType.name 16 | 17 | def checkstyle = project.tasks.create "checkstyle${name.capitalize()}", Checkstyle 18 | checkstyle.dependsOn variant.javaCompile 19 | checkstyle.source variant.javaCompile.source 20 | checkstyle.classpath = project.fileTree(variant.javaCompile.destinationDir) 21 | checkstyle.exclude('**/BuildConfig.java') 22 | checkstyle.exclude('**/R.java') 23 | project.tasks.getByName("check").dependsOn checkstyle 24 | } 25 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/ObjectRef.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import com.google.gson.GsonBuilder; 4 | 5 | import auto.parcel.AutoParcel; 6 | 7 | @AutoParcel 8 | public abstract class ObjectRef implements VinliItem { 9 | /*package*/ static final void registerGson(GsonBuilder gb) { 10 | gb.registerTypeAdapter(ObjectRef.class, AutoParcelAdapter.create(AutoParcel_ObjectRef.class)); 11 | } 12 | 13 | public static final Builder builder() { 14 | return new AutoParcel_ObjectRef.Builder(); 15 | } 16 | 17 | public abstract String type(); 18 | 19 | /*package*/ ObjectRef() { } 20 | 21 | @AutoParcel.Builder 22 | public static abstract class Builder { 23 | public abstract Builder id(String s); 24 | public abstract Builder type(String s); 25 | 26 | public abstract ObjectRef build(); 27 | 28 | /*package*/ Builder() { } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/VinliError.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | public final class VinliError extends RuntimeException { 4 | 5 | /*package*/ static VinliError serverError(ServerError err) { 6 | return new VinliError(err.message, err); 7 | } 8 | 9 | private final ServerError mServerError; 10 | 11 | /*package*/ VinliError(String message, ServerError err) { 12 | super(message); 13 | mServerError = err; 14 | } 15 | 16 | public int getStatusCode() { 17 | return mServerError == null ? -1 : mServerError.statusCode; 18 | } 19 | 20 | /*package*/ final class ServerError { 21 | private final int statusCode; 22 | private final String error, message; 23 | 24 | /*package*/ ServerError(int statusCode, String message) { 25 | this.statusCode = statusCode; 26 | this.message = message; 27 | this.error = null; // set by GSON 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /gradle/javadoc.gradle: -------------------------------------------------------------------------------- 1 | def variants = android.hasProperty('applicationVariants') \ 2 | ? android.applicationVariants \ 3 | : android.libraryVariants 4 | 5 | variants.all { variant -> 6 | def name = variant.buildType.name 7 | 8 | task("generate${name.capitalize()}JavaDoc", type: Javadoc) { 9 | description "Generates Javadoc for $name." 10 | source = variant.javaCompile.source 11 | 12 | if (project.hasProperty('destinationDir')) { 13 | destinationDir = file("${project.rootDir}/${project.destinationDir}"); 14 | } 15 | 16 | classpath = files(variant.javaCompile.classpath.files, android.bootClasspath) 17 | options.links("https://docs.oracle.com/javase/7/docs/api/"); 18 | options.linksOffline("http://d.android.com/reference", "${android.sdkDirectory}/docs/reference"); 19 | exclude '**/BuildConfig.java' 20 | exclude '**/R.java' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /deploy_website.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # taken from https://github.com/square/retrofit/blob/master/deploy_website.sh 4 | 5 | set -ex 6 | 7 | REPO="git@github.com:vinli/android-net.git" 8 | 9 | DIR=temp-clone 10 | 11 | # Delete any existing temporary website clone 12 | rm -rf $DIR 13 | 14 | # Clone the current repo into temp folder 15 | git clone $REPO $DIR 16 | 17 | # Move working directory into temp folder 18 | cd $DIR 19 | 20 | # Checkout and track the gh-pages branch 21 | git checkout -t origin/gh-pages 22 | 23 | # Delete everything 24 | rm -rf * 25 | 26 | # back to project 27 | cd .. 28 | 29 | # Generate the latest javadoc 30 | ./gradlew task generateReleaseJavaDoc '-PdestinationDir='$DIR 31 | cd $DIR 32 | 33 | # Stage all files in git and create a commit 34 | git add . 35 | git add -u 36 | git commit -m "Website at $(date)" 37 | 38 | # Push the new files up to GitHub 39 | git push origin gh-pages 40 | 41 | # Delete our temp folder 42 | cd .. 43 | # rm -rf $DIR 44 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Vehicles.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | import retrofit2.http.Query; 9 | import retrofit2.http.Url; 10 | import rx.Observable; 11 | 12 | /*package*/ interface Vehicles { 13 | 14 | @GET("devices/{deviceId}/vehicles") 15 | Observable> vehicles( 16 | @NonNull @Path("deviceId") String deviceId, 17 | @Nullable @Query("limit") Integer limit, 18 | @Nullable @Query("offset") Integer offset); 19 | 20 | @GET("devices/{deviceId}/vehicles/_latest") 21 | Observable> latestVehicle( 22 | @NonNull @Path("deviceId") String deviceId); 23 | 24 | @GET("vehicles/{vehicleId}") 25 | Observable> vehicle( 26 | @NonNull @Path("vehicleId") String vehicleId); 27 | 28 | @GET Observable> vehiclesForUrl(@NonNull @Url String url); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/DataItem.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import java.util.Collections; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | /*package*/ abstract class DataItem implements VinliItem { 11 | 12 | @Nullable /*package*/ abstract Map data(); 13 | 14 | public boolean hasValue(@NonNull String name) { 15 | final Map data = data(); 16 | if (data == null) { 17 | return false; 18 | } 19 | 20 | return data.containsKey(name); 21 | } 22 | 23 | @Nullable public String value(@NonNull String name) { 24 | final Map data = data(); 25 | if (data == null) { 26 | return null; 27 | } 28 | 29 | return data.get(name); 30 | } 31 | 32 | public Set dataKeys() { 33 | final Map data = data(); 34 | if (data == null) { 35 | return Collections.emptySet(); 36 | } 37 | 38 | return Collections.unmodifiableSet(data.keySet()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | vinli.groupId=li.vin 21 | vinli.artifactId=android-net 22 | vinli.githubRepo=android-net 23 | vinli.version=1.1.1 24 | vinli.name=Android Net SDK 25 | vinli.desc=Vinli Android Network SDK 26 | vinli.publish=true 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/AutoParcelAdapter.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.TypeAdapter; 5 | import com.google.gson.stream.JsonReader; 6 | import com.google.gson.stream.JsonWriter; 7 | 8 | import java.io.IOException; 9 | 10 | /*package*/ class AutoParcelAdapter extends TypeAdapter { 11 | public static final AutoParcelAdapter create(Class autoParcelClass) { 12 | return new AutoParcelAdapter(autoParcelClass); 13 | } 14 | 15 | private final Class autoParcelClass; 16 | private Gson gson; 17 | 18 | private AutoParcelAdapter(Class autoParcelClass) { 19 | this.autoParcelClass = autoParcelClass; 20 | } 21 | 22 | @Override public void write(JsonWriter out, T value) throws IOException { 23 | if (gson == null) { 24 | gson = Vinli.curApp().gson(); 25 | } 26 | 27 | gson.toJson(value, autoParcelClass, out); 28 | } 29 | 30 | @Override public T read(JsonReader in) throws IOException { 31 | if (gson == null) { 32 | gson = Vinli.curApp().gson(); 33 | } 34 | 35 | return gson.fromJson(in, autoParcelClass); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Locations.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import retrofit2.http.Url; 9 | import rx.Observable; 10 | 11 | /*package*/ interface Locations { 12 | 13 | @GET("devices/{deviceId}/locations") 14 | Observable> locations( 15 | @NonNull @Path("deviceId") String deviceId, 16 | @Nullable @Query("since") Long since, 17 | @Nullable @Query("until") Long until, 18 | @Nullable @Query("limit") Integer limit, 19 | @Nullable @Query("sortDir") String sortDir); 20 | 21 | @GET("vehicles/{vehicleId}/locations") 22 | Observable> vehicleLocations( 23 | @NonNull @Path("vehicleId") String vehicleId, 24 | @Nullable @Query("since") Long since, 25 | @Nullable @Query("until") Long until, 26 | @Nullable @Query("limit") Integer limit, 27 | @Nullable @Query("sortDir") String sortDir); 28 | 29 | @GET Observable> locationsForUrl(@NonNull @Url String url); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Diagnostics.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import retrofit2.http.Url; 9 | import rx.Observable; 10 | 11 | /*package*/ interface Diagnostics { 12 | 13 | @GET("vehicles/{vehicleId}/codes") 14 | Observable> codes(@NonNull @Path("vehicleId") String vehicleId, 15 | @Nullable @Query("since") Long since, 16 | @Nullable @Query("until") Long until, 17 | @Nullable @Query("limit") Integer limit, 18 | @Nullable @Query("sortDir") String sortDir, 19 | @Nullable @Query("state") String state); 20 | 21 | @GET("codes") 22 | Observable> diagnose(@NonNull @Query("number") String number); 23 | 24 | @GET("vehicles/{vehicleId}/battery_statuses/_current") 25 | Observable> currentBatteryStatus(@NonNull @Path("vehicleId") String vehicleId); 26 | 27 | @GET Observable> codesForUrl(@NonNull @Url String url); 28 | 29 | @GET Observable> rawCodesForUrl(@NonNull @Url String url); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Trips.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import retrofit2.http.Url; 9 | import rx.Observable; 10 | 11 | /*package*/ interface Trips { 12 | 13 | @GET("devices/{deviceId}/trips") 14 | Observable> trips( 15 | @NonNull @Path("deviceId") String deviceId, 16 | @Nullable @Query("since") Long since, 17 | @Nullable @Query("until") Long until, 18 | @Nullable @Query("limit") Integer limit, 19 | @Nullable @Query("sortDir") String sortDir); 20 | 21 | @GET("vehicles/{vehicleId}/trips") 22 | Observable> vehicleTrips( 23 | @NonNull @Path("vehicleId") String vehicleId, 24 | @Nullable @Query("since") Long since, 25 | @Nullable @Query("until") Long until, 26 | @Nullable @Query("limit") Integer limit, 27 | @Nullable @Query("sortDir") String sortDir); 28 | 29 | @GET("trips/{tripId}") 30 | Observable> trip(@NonNull @Path("tripId") String tripId); 31 | 32 | @GET Observable> tripsForUrl(@NonNull @Url String url); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Snapshots.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import retrofit2.http.Url; 9 | import rx.Observable; 10 | 11 | /*package*/ interface Snapshots { 12 | 13 | @GET("devices/{deviceId}/snapshots") 14 | Observable> snapshots( 15 | @NonNull @Path("deviceId") String deviceId, 16 | @NonNull @Query("fields") String fields, 17 | @Nullable @Query("since") Long since, 18 | @Nullable @Query("until") Long until, 19 | @Nullable @Query("limit") Integer limit, 20 | @Nullable @Query("sortDir") String sortDir); 21 | 22 | 23 | @GET("vehicles/{vehicleId}/snapshots") 24 | Observable> vehicleSnapshots( 25 | @NonNull @Path("vehicleId") String vehicleId, 26 | @NonNull @Query("fields") String fields, 27 | @Nullable @Query("since") Long since, 28 | @Nullable @Query("until") Long until, 29 | @Nullable @Query("limit") Integer limit, 30 | @Nullable @Query("sortDir") String sortDir); 31 | 32 | @GET Observable> snapshotsForUrl(@NonNull @Url String url); 33 | } 34 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Messages.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import retrofit2.http.Url; 9 | import rx.Observable; 10 | 11 | public interface Messages { 12 | 13 | @GET("devices/{deviceId}/messages") 14 | Observable> messages( 15 | @NonNull @Path("deviceId") String deviceId, 16 | @Nullable @Query("since") Long since, 17 | @Nullable @Query("until") Long until, 18 | @Nullable @Query("limit") Integer limit, 19 | @Nullable @Query("sortDir") String sortDir); 20 | 21 | 22 | @GET("vehicles/{vehicleId}/messages") 23 | Observable> vehicleMessages( 24 | @NonNull @Path("vehicleId") String vehicleId, 25 | @Nullable @Query("since") Long since, 26 | @Nullable @Query("until") Long until, 27 | @Nullable @Query("limit") Integer limit, 28 | @Nullable @Query("sortDir") String sortDir); 29 | 30 | @GET("messages/{messageId}") 31 | Observable> message( 32 | @NonNull @Path("messageId") String messageId); 33 | 34 | @GET Observable> messagesForUrl(@NonNull @Url String url); 35 | } 36 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Collisions.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | import retrofit2.http.Query; 9 | import retrofit2.http.Url; 10 | import rx.Observable; 11 | 12 | public interface Collisions { 13 | 14 | @GET("collisions/{collisionId}") 15 | Observable> collision( 16 | @NonNull @Path("collisionId") String collisionId); 17 | 18 | @GET("vehicles/{vehicleId}/collisions") 19 | Observable> collisionsForVehicle( 20 | @NonNull @Path("vehicleId") String vehicleId, 21 | @Nullable @Query("since") Long since, 22 | @Nullable @Query("until") Long until, 23 | @Nullable @Query("limit") Integer limit, 24 | @Nullable @Query("sortDir") String sortDir); 25 | 26 | @GET("devices/{deviceId}/collisions") 27 | Observable> collisionsForDevice( 28 | @NonNull @Path("deviceId") String deviceId, 29 | @Nullable @Query("since") Long since, 30 | @Nullable @Query("until") Long until, 31 | @Nullable @Query("limit") Integer limit, 32 | @Nullable @Query("sortDir") String sortDir); 33 | 34 | @GET Observable> collisionsForUrl(@NonNull @Url String url); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Notifications.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import retrofit2.http.Url; 9 | import rx.Observable; 10 | 11 | public interface Notifications { 12 | 13 | @GET("notifications/{notificationId}") Observable> notification( 14 | @NonNull @Path("notificationId") String notificationId); 15 | 16 | @GET("subscriptions/{subscriptionId}/notifications") 17 | Observable> notificationsForSubscription( 18 | @NonNull @Path("subscriptionId") String subscriptionId, @Nullable @Query("since") Long since, 19 | @Nullable @Query("until") Long until, @Nullable @Query("limit") Integer limit, 20 | @Nullable @Query("sortDir") String sortDir); 21 | 22 | @GET("events/{eventId}/notifications") 23 | Observable> notificationsForEvent( 24 | @NonNull @Path("eventId") String eventId, @Nullable @Query("since") Long since, 25 | @Nullable @Query("until") Long until, @Nullable @Query("limit") Integer limit, 26 | @Nullable @Query("sortDir") String sortDir); 27 | 28 | @GET Observable> notificationsForUrl(@NonNull @Url String url); 29 | } 30 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Dummies.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.Body; 6 | import retrofit2.http.DELETE; 7 | import retrofit2.http.GET; 8 | import retrofit2.http.POST; 9 | import retrofit2.http.Path; 10 | import retrofit2.http.Query; 11 | import retrofit2.http.Url; 12 | import rx.Observable; 13 | 14 | /** 15 | * Created by JoshBeridon on 11/18/16. 16 | */ 17 | 18 | /*package*/ interface Dummies { 19 | 20 | @GET("dummies") 21 | Observable> dummies( 22 | @Nullable @Query("limit") Integer limit, 23 | @Nullable @Query("offset") Integer offset); 24 | 25 | @POST("dummies/{dummyId}/runs") 26 | Observable> create( 27 | @NonNull @Path("dummyId") String dummyId, 28 | @NonNull @Body Dummy.Run.Seed runSeed); 29 | 30 | @GET("dummies/{dummyId}/runs/_current") 31 | Observable> currentRun( 32 | @NonNull @Path("dummyId") String dummyId); 33 | 34 | @GET("dummies/{dummyId}") 35 | Observable> trip(@NonNull @Path("dummyId") String dummyId); 36 | 37 | @DELETE("dummies/{dummyId}/runs/_current") 38 | Observable deleteRun(@NonNull @Path("dummyId") String dummyId); 39 | 40 | @GET Observable> dummiesForUrl(@NonNull @Url String url); 41 | } 42 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/VinliBaseActivity.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.app.Activity; 4 | import android.app.PendingIntent; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.v7.app.AppCompatActivity; 9 | 10 | public abstract class VinliBaseActivity extends AppCompatActivity{ 11 | 12 | 13 | private VinliSignInHandler signInHandler; 14 | public VinliApp vinliApp; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | 20 | signInHandler = new VinliSignInHandler(); 21 | } 22 | 23 | @Override 24 | protected void onResume(){ 25 | super.onResume(); 26 | vinliApp = signInHandler.handleOnResume(this, getIntent()); 27 | } 28 | 29 | @Override 30 | protected void onNewIntent(Intent intent){ 31 | super.onNewIntent(intent); 32 | vinliApp = signInHandler.handleOnNewIntent(this, intent); 33 | } 34 | 35 | @Override 36 | protected void onDestroy(){ 37 | super.onDestroy(); 38 | } 39 | 40 | public void signIn(@NonNull String clientId, @NonNull String redirectURI, @NonNull PendingIntent pendingIntent){ 41 | signInHandler.signIn(this, clientId, redirectURI, pendingIntent); 42 | } 43 | 44 | public boolean signedIn(){ 45 | return signInHandler.signedIn(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/User.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.google.gson.GsonBuilder; 8 | import com.google.gson.reflect.TypeToken; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | import auto.parcel.AutoParcel; 13 | 14 | @AutoParcel 15 | public abstract class User implements VinliItem { 16 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { }.getType(); 17 | 18 | /*package*/ static final void registerGson(GsonBuilder gb) { 19 | gb.registerTypeAdapter(User.class, AutoParcelAdapter.create(AutoParcel_User.class)); 20 | gb.registerTypeAdapter(Settings.class, AutoParcelAdapter.create(AutoParcel_User_Settings.class)); 21 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(User.class)); 22 | } 23 | 24 | @NonNull public abstract String firstName(); 25 | @NonNull public abstract String lastName(); 26 | @NonNull public abstract String email(); 27 | @Nullable public abstract String image(); 28 | @NonNull public abstract String phone(); 29 | @NonNull public abstract String createdAt(); 30 | @Nullable public abstract Settings settings(); 31 | 32 | /*package*/ User() { } 33 | 34 | @AutoParcel 35 | public static abstract class Settings implements Parcelable { 36 | @Nullable public abstract String unit(); 37 | 38 | /*package*/ Settings() { } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Events.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import retrofit2.http.Url; 9 | import rx.Observable; 10 | 11 | /*package*/ interface Events { 12 | 13 | @GET("devices/{deviceId}/events") 14 | Observable> events( 15 | @NonNull @Path("deviceId") String deviceId, 16 | @Nullable @Query("type") String type, 17 | @Nullable @Query("objectId") String objectId, 18 | @Nullable @Query("since") Long since, 19 | @Nullable @Query("until") Long until, 20 | @Nullable @Query("limit") Integer limit, 21 | @Nullable @Query("sortDir") String sortDir); 22 | 23 | @GET("events/{eventId}") 24 | Observable> event(@NonNull @Path("eventId") String eventId); 25 | 26 | @GET Observable> eventsForUrl(@NonNull @Url String url); 27 | 28 | 29 | 30 | @GET("vehicles/{vehicleId}/events") 31 | Observable> vehicleEvents( 32 | @NonNull @Path("vehicleId") String vehicleId, 33 | @Nullable @Query("type") String type, 34 | @Nullable @Query("objectId") String objectId, 35 | @Nullable @Query("since") Long since, 36 | @Nullable @Query("until") Long until, 37 | @Nullable @Query("limit") Integer limit, 38 | @Nullable @Query("sortDir") String sortDir); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /android-net/src/test/java/li/vin/net/UsersIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.robolectric.RobolectricTestRunner; 7 | import org.robolectric.annotation.Config; 8 | 9 | import rx.Subscriber; 10 | 11 | import static junit.framework.Assert.assertTrue; 12 | 13 | @RunWith(RobolectricTestRunner.class) 14 | @Config(constants = BuildConfig.class, sdk = 22) 15 | public class UsersIntegrationTests { 16 | 17 | public VinliApp vinliApp; 18 | 19 | @Before 20 | public void setup(){ 21 | assertTrue(TestHelper.getAccessToken() != null); 22 | 23 | vinliApp = TestHelper.getVinliApp(); 24 | } 25 | 26 | @Test 27 | public void testGetUser(){ 28 | vinliApp.currentUser().toBlocking().subscribe(new Subscriber() { 29 | @Override 30 | public void onCompleted() { 31 | 32 | } 33 | 34 | @Override 35 | public void onError(Throwable e) { 36 | System.out.println("Error: " + e.getMessage()); 37 | e.printStackTrace(); 38 | assertTrue(false); 39 | } 40 | 41 | @Override 42 | public void onNext(User user) { 43 | assertTrue(user.id() != null && user.id().length() > 0); 44 | assertTrue(user.firstName().length() > 0); 45 | assertTrue(user.lastName().length() > 0); 46 | assertTrue(user.email().length() > 0); 47 | assertTrue(user.phone().length() > 0); 48 | } 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Rules.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import retrofit2.http.Body; 6 | import retrofit2.http.DELETE; 7 | import retrofit2.http.GET; 8 | import retrofit2.http.POST; 9 | import retrofit2.http.Path; 10 | import retrofit2.http.Query; 11 | import retrofit2.http.Url; 12 | import rx.Observable; 13 | 14 | /*package*/ interface Rules { 15 | 16 | @GET("devices/{deviceId}/rules") 17 | Observable> rules( 18 | @NonNull @Path("deviceId") String deviceId, 19 | @Nullable @Query("limit") Integer limit, 20 | @Nullable @Query("offset") Integer offset); 21 | 22 | @GET("vehicles/{vehicleId}/rules") 23 | Observable> vehicleRules( 24 | @NonNull @Path("vehicleId") String vehicleId, 25 | @Nullable @Query("limit") Integer limit, 26 | @Nullable @Query("offset") Integer offset); 27 | 28 | @GET("rules/{ruleId}") 29 | Observable> rule( 30 | @NonNull @Path("ruleId") String ruleId); 31 | 32 | @POST("devices/{deviceId}/rules") 33 | Observable> create( 34 | @NonNull @Path("deviceId") String deviceId, 35 | @NonNull @Body Rule.Seed ruleSeed); 36 | 37 | @POST("vehicles/{vehicleId}/rules") 38 | Observable> vehicleCreate( 39 | @NonNull @Path("vehicleId") String vehicleId, 40 | @NonNull @Body Rule.Seed ruleSeed); 41 | 42 | @DELETE("rules/{ruleId}") 43 | Observable delete(@NonNull @Path("ruleId") String ruleId); 44 | 45 | @GET Observable> rulesForUrl(@NonNull @Url String url); 46 | } 47 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/ReportCards.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | import retrofit2.http.Query; 9 | import retrofit2.http.Url; 10 | import rx.Observable; 11 | 12 | public interface ReportCards { 13 | 14 | @GET("report_cards/{reportCardId}") Observable> reportCard( 15 | @NonNull @Path("reportCardId") String reportCardId); 16 | 17 | @GET("vehicles/{vehicleId}/report_cards") 18 | Observable> reportCardsForVehicle( 19 | @NonNull @Path("vehicleId") String vehicleId, @Nullable @Query("since") Long since, 20 | @Nullable @Query("until") Long until, @Nullable @Query("limit") Integer limit, 21 | @Nullable @Query("sortDir") String sortDir); 22 | 23 | @GET("devices/{deviceId}/report_cards") Observable> reportCardsForDevice( 24 | @NonNull @Path("deviceId") String deviceId, @Nullable @Query("since") Long since, 25 | @Nullable @Query("until") Long until, @Nullable @Query("limit") Integer limit, 26 | @Nullable @Query("sortDir") String sortDir); 27 | 28 | @GET("devices/{deviceId}/report_cards/overall") 29 | Observable overallReportCardForDevice( 30 | @NonNull @Path("deviceId") String deviceId); 31 | 32 | @GET("trips/{tripId}/report_cards/_current") Observable> reportCardForTrip( 33 | @NonNull @Path("tripId") String tripId); 34 | 35 | @GET Observable> reportCardsForUrl(@NonNull @Url String url); 36 | } 37 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/VinliSignInHandler.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.app.Activity; 4 | import android.app.PendingIntent; 5 | import android.content.Intent; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | 9 | public class VinliSignInHandler { 10 | 11 | private boolean signInRequested; 12 | private VinliApp vinliApp; 13 | 14 | public VinliApp handleOnResume(@NonNull Activity context, @Nullable Intent intent){ 15 | loadApp(context, intent); 16 | return vinliApp; 17 | } 18 | 19 | public VinliApp handleOnNewIntent(@NonNull Activity context, @Nullable Intent intent){ 20 | loadApp(context, intent); 21 | return vinliApp; 22 | } 23 | 24 | public void handleOnDestroy(Activity context){ 25 | 26 | } 27 | 28 | private void loadApp(@NonNull Activity context, @Nullable Intent intent){ 29 | if (vinliApp == null) { 30 | vinliApp = intent == null 31 | ? Vinli.loadApp(context) 32 | : Vinli.initApp(context, intent); 33 | if (vinliApp == null) { 34 | if (signInRequested) { 35 | // If a sign in was already requested, it failed or was canceled - finish. 36 | context.finish(); 37 | } 38 | } 39 | } 40 | } 41 | 42 | public void signIn(@NonNull Activity context, @NonNull String clientId, @NonNull String redirectURI, @NonNull PendingIntent pendingIntent){ 43 | signInRequested = true; 44 | context.setIntent(new Intent()); 45 | Vinli.clearApp(context); 46 | vinliApp = null; 47 | Vinli.signIn(context, clientId, redirectURI, pendingIntent); 48 | } 49 | 50 | public VinliApp getVinliApp(){ 51 | return vinliApp; 52 | } 53 | 54 | public boolean signedIn(){ 55 | return (vinliApp != null); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/BatteryStatus.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import auto.parcel.AutoParcel; 7 | import com.google.gson.GsonBuilder; 8 | import com.google.gson.annotations.SerializedName; 9 | import com.google.gson.reflect.TypeToken; 10 | import java.lang.reflect.Type; 11 | import rx.Observable; 12 | 13 | @AutoParcel public abstract class BatteryStatus implements Parcelable { 14 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { 15 | }.getType(); 16 | 17 | /*package*/ 18 | static final void registerGson(GsonBuilder gb) { 19 | gb.registerTypeAdapter(BatteryStatus.class, 20 | AutoParcelAdapter.create(AutoParcel_BatteryStatus.class)); 21 | gb.registerTypeAdapter(WRAPPED_TYPE, 22 | Wrapped.Adapter.create(BatteryStatus.class, "batteryStatus")); 23 | } 24 | 25 | @Nullable public abstract BatteryStatusColor status(); 26 | 27 | @NonNull public abstract String timestamp(); 28 | 29 | /*package*/ BatteryStatus() { 30 | } 31 | 32 | public static Observable currentBatteryStatusForVehicle( 33 | @NonNull String vehicleId) { 34 | return Vinli.curApp() 35 | .diagnostics() 36 | .currentBatteryStatus(vehicleId) 37 | .map(Wrapped.pluckItem()); 38 | } 39 | 40 | public enum BatteryStatusColor { 41 | @SerializedName("green")GREEN, // indicates a battery voltage reading greater than 11.75 volts 42 | @SerializedName("yellow")YELLOW, // indicates a battery voltage reading less than 11.75 volts and greater than 11.31 volts 43 | @SerializedName("red")RED // indicates a batter voltage reading less than or equal to 11.31 volts 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/VinliEndpoint.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import okhttp3.HttpUrl; 6 | 7 | public enum VinliEndpoint { 8 | AUTH("auth"), 9 | DIAGNOSTICS("diagnostic"), 10 | EVENTS("events"), 11 | PLATFORM("platform"), 12 | RULES("rules"), 13 | TELEMETRY("telemetry"), 14 | TRIPS("trips"), 15 | SAFETY("safety"), 16 | BEHAVIORAL("behavioral"), 17 | DISTANCE("distance"), 18 | DUMMY("dummies"); 19 | 20 | static final String DOMAIN_QA = "-qa."; 21 | static final String DOMAIN_DEV = "-dev."; 22 | static final String DOMAIN_DEMO = "-demo."; 23 | static final String DOMAIN_PROD = "."; 24 | 25 | static private String host = "vin.li"; 26 | static private String domain = DOMAIN_PROD; 27 | 28 | static synchronized String domain() { 29 | return domain + host; 30 | } 31 | 32 | public static synchronized void setHost(@NonNull String host) { 33 | VinliEndpoint.host = host; 34 | } 35 | 36 | public static synchronized void setDomain(@NonNull String domain) { 37 | VinliEndpoint.domain = domain; 38 | } 39 | 40 | private final HttpUrl mUrl; 41 | private final String subDomain; 42 | 43 | VinliEndpoint(String subDomain) { 44 | this.subDomain = subDomain; 45 | mUrl = new HttpUrl.Builder() 46 | .scheme("https") 47 | .host(subDomain + domain()) 48 | .addPathSegment("api") 49 | .addPathSegment("v1") 50 | .addPathSegment("") 51 | .build(); 52 | } 53 | 54 | public String getName() { 55 | return this.name(); 56 | } 57 | 58 | public String getUrl() { 59 | return mUrl.newBuilder().host(subDomain + domain()).toString(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /gradle/mavenize.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | 3 | install { 4 | repositories.mavenInstaller { 5 | pom.project { 6 | packaging project.hasProperty('vinli.packaging') \ 7 | ? project.property('vinli.packaging') \ 8 | : 'aar' 9 | groupId project.property('vinli.groupId') 10 | artifactId project.property('vinli.artifactId') 11 | version project.property('vinli.version') 12 | 13 | name project.property('vinli.name') 14 | description project.property('vinli.desc') 15 | url "https://github.com/vinli/${project.property('vinli.githubRepo')}" 16 | 17 | scm { 18 | url "https://github.com/vinli/${project.property('vinli.githubRepo')}" 19 | connection "scm:git:git://github.com/vinli/${project.property('vinli.githubRepo')}.git" 20 | developerConnection "scm:git:ssh://git@github.com/vinli/${project.property('vinli.githubRepo')}.git" 21 | tag project.property('vinli.version') 22 | } 23 | 24 | issueManagement { 25 | system 'GitHub Issues' 26 | url "https://github.com/vinli/${project.property('vinli.githubRepo')}/issues" 27 | } 28 | 29 | licenses { 30 | license { 31 | name 'MIT' 32 | url 'http://opensource.org/licenses/MIT' 33 | } 34 | } 35 | 36 | organization { 37 | name 'Vinli, Inc.' 38 | url 'https://vin.li' 39 | } 40 | 41 | developers { 42 | developer { 43 | id 'kturney' 44 | name 'Kyle Turney' 45 | } 46 | developer { 47 | id 'cmc5788' 48 | name 'Christopher Casey' 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | uploadArchives { 56 | repositories.mavenDeployer { 57 | pom.artifactId = project.property('vinli.artifactId') 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/utils/RxAdapter.java: -------------------------------------------------------------------------------- 1 | package li.vin.net.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.util.Log; 5 | import android.widget.BaseAdapter; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import rx.Observable; 9 | import rx.Subscriber; 10 | import rx.android.schedulers.AndroidSchedulers; 11 | 12 | public abstract class RxAdapter extends BaseAdapter { 13 | private Observable observable; 14 | private rx.Subscription curSubscription; 15 | private List items = new ArrayList<>(); 16 | 17 | private final Subscriber subscriber = new Subscriber() { 18 | @Override public void onCompleted() { 19 | RxAdapter.this.onComplete(); 20 | } 21 | 22 | @Override public void onError(Throwable e) { 23 | RxAdapter.this.onError(e); 24 | } 25 | 26 | @Override public void onNext(T t) { 27 | RxAdapter.this.onNext(t); 28 | items.add(t); 29 | RxAdapter.this.notifyDataSetChanged(); 30 | } 31 | }; 32 | 33 | @Override public int getCount() { 34 | return items.size(); 35 | } 36 | 37 | @Override public T getItem(int position) { 38 | return items.get(position); 39 | } 40 | 41 | protected void onComplete() { 42 | Log.e(this.getClass().getSimpleName(), "onComplete"); 43 | } 44 | 45 | protected void onError(Throwable e) { 46 | Log.e(this.getClass().getSimpleName(), "onError", e); 47 | } 48 | 49 | protected void onNext(T t) { } 50 | 51 | public rx.Subscription subscribe(@NonNull Observable observable) { 52 | return subscribe(null, observable); 53 | } 54 | 55 | /** Use {@link #subscribe(Observable)} instead. */ 56 | @Deprecated 57 | public rx.Subscription subscribe(Object context, @NonNull Observable observable) { 58 | if (curSubscription != null && !curSubscription.isUnsubscribed()) { 59 | curSubscription.unsubscribe(); 60 | } 61 | 62 | this.observable = bind(observable); 63 | 64 | curSubscription = bind(observable).subscribe(subscriber); 65 | 66 | if (!items.isEmpty()) { 67 | items.clear(); 68 | this.notifyDataSetChanged(); 69 | } 70 | 71 | return curSubscription; 72 | } 73 | 74 | private static final Observable bind(@NonNull Observable observable) { 75 | return observable.observeOn(AndroidSchedulers.mainThread()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Subscriptions.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import retrofit2.http.Body; 7 | import retrofit2.http.DELETE; 8 | import retrofit2.http.GET; 9 | import retrofit2.http.POST; 10 | import retrofit2.http.PUT; 11 | import retrofit2.http.Path; 12 | import retrofit2.http.Query; 13 | import retrofit2.http.Url; 14 | import rx.Observable; 15 | 16 | /*package*/ interface Subscriptions { 17 | 18 | @GET("devices/{deviceId}/subscriptions") 19 | Observable> subscriptions( 20 | @NonNull @Path("deviceId") String deviceId, 21 | @Nullable @Query("limit") Integer limit, 22 | @Nullable @Query("offset") Integer offset, 23 | @Nullable @Query("objectId") String objectId, 24 | @Nullable @Query("objectType") String objectType); 25 | 26 | @GET("vehicles/{vehicleId}/subscriptions") 27 | Observable> vehicleSubscriptions( 28 | @NonNull @Path("vehicleId") String vehicleId, 29 | @Nullable @Query("limit") Integer limit, 30 | @Nullable @Query("offset") Integer offset, 31 | @Nullable @Query("objectId") String objectId, 32 | @Nullable @Query("objectType") String objectType); 33 | 34 | @POST("vehicles/{vehicleId}/subscriptions") 35 | Observable> vehicleCreate( 36 | @NonNull @Path("vehicleId") String vehicleId, 37 | @NonNull @Body Subscription.SeedCreate seedCreate); 38 | 39 | @GET("subscriptions/{subscriptionId}") 40 | Observable> subscription( 41 | @NonNull @Path("subscriptionId") String subscriptionId); 42 | 43 | @POST("devices/{deviceId}/subscriptions") 44 | Observable> create( 45 | @NonNull @Path("deviceId") String deviceId, 46 | @NonNull @Body Subscription.SeedCreate seedCreate); 47 | 48 | @PUT("devices/{deviceId}/subscriptions/{subscriptionId}") 49 | Observable> edit( 50 | @NonNull @Path("deviceId") String deviceId, 51 | @NonNull @Path("subscriptionId") String subscriptionId, 52 | @NonNull @Body Subscription.SeedEdit seedEdit); 53 | 54 | @DELETE("subscriptions/{subscriptionId}") 55 | Observable delete(@NonNull @Path("subscriptionId") String subscriptionId); 56 | 57 | @GET Observable> subscriptionsForUrl(@NonNull @Url String url); 58 | 59 | } 60 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Wrapped.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.TypeAdapter; 8 | import com.google.gson.stream.JsonReader; 9 | import com.google.gson.stream.JsonWriter; 10 | 11 | import java.io.IOException; 12 | import java.util.Locale; 13 | 14 | import auto.parcel.AutoParcel; 15 | import rx.functions.Func1; 16 | 17 | @AutoParcel 18 | /*package*/ abstract class Wrapped { 19 | public static final Func1 PLUCK_ITEM = new Func1, Object>() { 20 | @Override public Object call(Wrapped tWrapped) { 21 | return tWrapped.item(); 22 | } 23 | }; 24 | 25 | @SuppressWarnings("unchecked") 26 | public static final Func1, T> pluckItem() { 27 | return PLUCK_ITEM; 28 | } 29 | 30 | public static final Wrapped create(@NonNull T item) { 31 | return new AutoParcel_Wrapped<>(item); 32 | } 33 | 34 | @Nullable public abstract T item(); 35 | 36 | /*package*/ static final class Adapter extends TypeAdapter> { 37 | public static final Adapter create(Class itemCls) { 38 | return create(itemCls, itemCls.getSimpleName().toLowerCase(Locale.US)); 39 | } 40 | 41 | public static final Adapter create(Class itemCls, String itemName) { 42 | return new Adapter<>(itemCls, itemName); 43 | } 44 | 45 | private final String itemName; 46 | private final Class wrappedCls; 47 | 48 | private Gson gson; 49 | 50 | private Adapter(Class wrappedCls, String itemName) { 51 | this.itemName = itemName; 52 | this.wrappedCls = wrappedCls; 53 | } 54 | 55 | @Override public void write(JsonWriter out, Wrapped value) throws IOException { 56 | if (gson == null) { 57 | gson = Vinli.curApp().gson(); 58 | } 59 | 60 | out.beginObject(); 61 | out.name(itemName); gson.toJson(value.item(), wrappedCls, out); 62 | out.endObject(); 63 | } 64 | 65 | @Override public Wrapped read(JsonReader in) throws IOException { 66 | if (gson == null) { 67 | gson = Vinli.curApp().gson(); 68 | } 69 | 70 | in.beginObject(); 71 | 72 | final String name = in.nextName(); 73 | if (!itemName.equals(name)) { 74 | throw new IOException(name + " does not match expected name " + itemName); 75 | } 76 | 77 | final T item = gson.fromJson(in, wrappedCls); 78 | 79 | in.endObject(); 80 | 81 | return new AutoParcel_Wrapped<>(item); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Distances.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import retrofit2.http.Body; 5 | import retrofit2.http.DELETE; 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Header; 8 | import retrofit2.http.POST; 9 | import retrofit2.http.Path; 10 | import retrofit2.http.Query; 11 | import retrofit2.http.Url; 12 | import rx.Observable; 13 | 14 | public interface Distances { 15 | 16 | @GET("vehicles/{vehicleId}/distances") 17 | Observable distances( 18 | @Path("vehicleId") String vehicleId, 19 | @Query("since") Long since, 20 | @Query("until") Long until, 21 | @Header("x-vinli-unit") String unit); 22 | 23 | @GET("vehicles/{vehicleId}/distances/_best") 24 | Observable> bestDistance( 25 | @Path("vehicleId") String vehicleId, 26 | @Header("x-vinli-unit") String unit); 27 | 28 | @POST("vehicles/{vehicleId}/odometers") 29 | Observable> createOdometerReport( 30 | @Path("vehicleId") String vehicleId, 31 | @Body Odometer.Seed odometerSeed); 32 | 33 | @GET("vehicles/{vehicleId}/odometers") 34 | Observable> odometerReports( 35 | @Path("vehicleId") String vehicleId, 36 | @Query("since") Long since, 37 | @Query("until") Long until, 38 | @Query("limit") Integer limit, 39 | @Query("sortDir") String sortDir); 40 | 41 | @GET("odometers/{odometerId}") 42 | Observable> odometerReport( 43 | @Path("odometerId") String odometerId); 44 | 45 | @DELETE("odometers/{odometerId}") 46 | Observable deleteOdometerReport( 47 | @Path("odometerId") String odometerId); 48 | 49 | @GET Observable> odometerReportsForUrl(@NonNull @Url String url); 50 | 51 | @POST("vehicles/{vehicleId}/odometer_triggers") 52 | Observable> createOdometerTrigger( 53 | @Path("vehicleId") String vehicleId, 54 | @Body OdometerTrigger.Seed odometerTrigger); 55 | 56 | @GET("odometer_triggers/{odometerTriggerId}") 57 | Observable> odometerTrigger( 58 | @Path("odometerTriggerId") String odometerTriggerId); 59 | 60 | @DELETE("odometer_triggers/{odometerTriggerId}") 61 | Observable deleteOdometerTrigger( 62 | @Path("odometerTriggerId") String odometerTriggerId); 63 | 64 | @GET("vehicles/{vehicleId}/odometer_triggers") 65 | Observable> odometerTriggers( 66 | @Path("vehicleId") String vehicleId, 67 | @Query("since") Long since, 68 | @Query("until") Long until, 69 | @Query("limit") Integer limit, 70 | @Query("sortDir") String sortDir); 71 | 72 | @GET Observable> odometerTriggersForUrl(@NonNull @Url String url); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/ObserverManager.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Looper; 4 | import android.support.annotation.NonNull; 5 | import java.util.HashSet; 6 | import java.util.Iterator; 7 | import java.util.Set; 8 | import rx.Observable; 9 | import rx.Observer; 10 | import rx.Subscription; 11 | import rx.android.schedulers.AndroidSchedulers; 12 | 13 | /** 14 | * Created by christophercasey on 8/3/15. 15 | * 16 | * Wraps Observable subscriptions in simple managed observables that can automatically bind to 17 | * Activities or Fragments and observe on the UI thread. 18 | */ 19 | /*package*/ final class ObserverManager { 20 | 21 | /** Register an observer. Always observes on the UI thread. Will throw an unchecked exception 22 | * if observer is already registered. */ 23 | /*package*/ static void registerObserver(@NonNull Observer cb, 24 | @NonNull Observable observable) { 25 | if (Thread.currentThread() != Looper.getMainLooper().getThread()) { 26 | throw new IllegalStateException("Must be called on UI thread."); 27 | } 28 | Subscription sub = observable.observeOn(AndroidSchedulers.mainThread()).subscribe(cb); 29 | if (!callbacks().add(new CallbackSubscriptionTuple(cb, sub))) { 30 | throw new IllegalStateException("callback already registered."); 31 | } 32 | } 33 | 34 | /** Permissively attempt to unregister an already-registered observer. */ 35 | /*package*/ static void unregisterObserver(@NonNull Observer cb) { 36 | if (Thread.currentThread() != Looper.getMainLooper().getThread()) { 37 | throw new IllegalStateException("Must be called on UI thread."); 38 | } 39 | for (Iterator i=callbacks().iterator(); i.hasNext(); ) { 40 | CallbackSubscriptionTuple cbSub = i.next(); 41 | if (cbSub.cb.equals(cb)) { 42 | cbSub.sub.unsubscribe(); 43 | i.remove(); 44 | return; 45 | } 46 | } 47 | } 48 | 49 | private static final class InitOnDemandHolder { 50 | private static final Set sCallbacks = new HashSet<>(); 51 | } 52 | 53 | private static Set callbacks() { 54 | return InitOnDemandHolder.sCallbacks; 55 | } 56 | 57 | private static final class CallbackSubscriptionTuple { 58 | final Observer cb; 59 | final rx.Subscription sub; 60 | 61 | CallbackSubscriptionTuple(Observer cb, rx.Subscription sub) { 62 | this.cb = cb; 63 | this.sub = sub; 64 | } 65 | 66 | @Override public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null || getClass() != o.getClass()) return false; 69 | 70 | CallbackSubscriptionTuple that = (CallbackSubscriptionTuple) o; 71 | 72 | return cb.equals(that.cb); 73 | } 74 | 75 | @Override public int hashCode() { 76 | return cb.hashCode(); 77 | } 78 | } 79 | 80 | private ObserverManager() { } 81 | } 82 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/DistanceList.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import auto.parcel.AutoParcel; 7 | import com.google.gson.GsonBuilder; 8 | import com.google.gson.reflect.TypeToken; 9 | 10 | import java.lang.reflect.Type; 11 | import java.util.Date; 12 | import java.util.List; 13 | import rx.Observable; 14 | 15 | @AutoParcel 16 | public abstract class DistanceList implements VinliItem{ 17 | 18 | /*package*/ static final void registerGson(GsonBuilder gb) { 19 | gb.registerTypeAdapter(DistanceList.class, AutoParcelAdapter.create(AutoParcel_DistanceList.class)); 20 | 21 | Distance.registerGson(gb); 22 | } 23 | 24 | public abstract List distances(); 25 | 26 | public static Observable distancesWithVehicleId(@NonNull String vehicleId) { 27 | return distancesWithVehicleId(vehicleId, (Long) null, null, null); 28 | } 29 | 30 | public static Observable distancesWithVehicleId(@NonNull String vehicleId, 31 | @Nullable Long sinceMs, @Nullable Long untilMs, @Nullable DistanceUnit unit) { 32 | return Vinli.curApp() 33 | .distances() 34 | .distances(vehicleId, sinceMs, untilMs, (unit == null) ? null : unit.getDistanceUnitStr()); 35 | } 36 | 37 | @Deprecated 38 | public static Observable distancesWithVehicleId(@NonNull String vehicleId, 39 | @Nullable Date since, @Nullable Date until, @Nullable DistanceUnit unit) { 40 | Long sinceMs = since == null ? null : since.getTime(); 41 | Long untilMs = until == null ? null : until.getTime(); 42 | return Vinli.curApp() 43 | .distances() 44 | .distances(vehicleId, sinceMs, untilMs, (unit == null) ? null : unit.getDistanceUnitStr()); 45 | } 46 | 47 | @AutoParcel 48 | public static abstract class Distance implements Parcelable{ 49 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { }.getType(); 50 | 51 | /*package*/ static final void registerGson(GsonBuilder gb) { 52 | gb.registerTypeAdapter(Distance.class, AutoParcelAdapter.create(AutoParcel_DistanceList_Distance.class)); 53 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(Distance.class)); 54 | } 55 | 56 | public abstract Double confidenceMin(); 57 | public abstract Double confidenceMax(); 58 | public abstract Double value(); 59 | public abstract String lastOdometerDate(); 60 | 61 | public static Observable bestDistanceWithVehicleId(@NonNull String vehicleId) { 62 | return bestDistanceWithVehicleId(vehicleId, null); 63 | } 64 | 65 | public static Observable bestDistanceWithVehicleId(@NonNull String vehicleId, 66 | @Nullable DistanceUnit unit) { 67 | return Vinli.curApp() 68 | .distances() 69 | .bestDistance(vehicleId, (unit == null) ? null : unit.getDistanceUnitStr()) 70 | .map(Wrapped.pluckItem()); 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Coordinate.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.GsonBuilder; 8 | import com.google.gson.TypeAdapter; 9 | import com.google.gson.stream.JsonReader; 10 | import com.google.gson.stream.JsonWriter; 11 | 12 | import java.io.IOException; 13 | 14 | import auto.parcel.AutoParcel; 15 | 16 | @AutoParcel 17 | public abstract class Coordinate implements Parcelable { 18 | /*package*/ static final void registerGson(GsonBuilder gb) { 19 | gb.registerTypeAdapter( 20 | Coordinate.class, 21 | new CoordinateAdapter()); 22 | 23 | gb.registerTypeAdapter(AutoParcel_Coordinate.Seed.class, new Seed.Adapter()); 24 | } 25 | 26 | /*package*/ static final Builder builder() { 27 | return new AutoParcel_Coordinate.Builder(); 28 | } 29 | 30 | public abstract float lon(); 31 | public abstract float lat(); 32 | 33 | public static final Seed.Builder create(){ 34 | return new AutoParcel_Coordinate_Seed.Builder(); 35 | } 36 | 37 | @AutoParcel.Builder 38 | /*package*/ interface Builder { 39 | Builder lon(float f); 40 | Builder lat(float f); 41 | 42 | Coordinate build(); 43 | } 44 | 45 | /*package*/ Coordinate() { } 46 | 47 | @AutoParcel 48 | public static abstract class Seed{ 49 | @NonNull public abstract float lon(); 50 | @NonNull public abstract float lat(); 51 | 52 | /*package*/ Seed() {} 53 | 54 | @AutoParcel.Builder 55 | public static abstract class Builder{ 56 | public abstract Builder lat(@NonNull float latitude); 57 | public abstract Builder lon(@NonNull float longitude); 58 | 59 | public abstract Seed build(); 60 | 61 | /*package*/ Builder(){} 62 | } 63 | 64 | /*package */ static final class Adapter extends TypeAdapter{ 65 | 66 | @Override 67 | public void write(com.google.gson.stream.JsonWriter out, Seed value) throws IOException { 68 | out.beginArray(); 69 | out.value(value.lon()); 70 | out.value(value.lat()); 71 | out.endArray(); 72 | } 73 | 74 | @Override public Coordinate.Seed read(JsonReader in) throws IOException { 75 | throw new UnsupportedOperationException("reading a Coordinate.Seed is not supported"); 76 | } 77 | } 78 | } 79 | 80 | private static final class CoordinateAdapter extends TypeAdapter { 81 | 82 | @Override public void write(JsonWriter out, Coordinate value) throws IOException { 83 | out.beginArray(); 84 | out.value(value.lon()); 85 | out.value(value.lat()); 86 | out.endArray(); 87 | } 88 | 89 | @Override public Coordinate read(JsonReader in) throws IOException { 90 | final Coordinate.Builder b = Coordinate.builder(); 91 | 92 | in.beginArray(); 93 | b.lon((float) in.nextDouble()); 94 | b.lat((float) in.nextDouble()); 95 | in.endArray(); 96 | 97 | return b.build(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Dtc.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import auto.parcel.AutoParcel; 5 | import com.google.gson.GsonBuilder; 6 | import com.google.gson.reflect.TypeToken; 7 | import java.lang.reflect.Type; 8 | import rx.Observable; 9 | 10 | @AutoParcel 11 | public abstract class Dtc implements VinliItem { 12 | /*package*/ static final Type TIME_SERIES_TYPE = new TypeToken>() { }.getType(); 13 | 14 | /*package*/ static final void registerGson(GsonBuilder gb) { 15 | gb.registerTypeAdapter(Dtc.class, AutoParcelAdapter.create(AutoParcel_Dtc.class)); 16 | gb.registerTypeAdapter(Links.class, AutoParcelAdapter.create(AutoParcel_Dtc_Links.class)); 17 | gb.registerTypeAdapter(Code.class, AutoParcelAdapter.create(AutoParcel_Dtc_Code.class)); 18 | gb.registerTypeAdapter(Code.TwoByte.class, AutoParcelAdapter.create(AutoParcel_Dtc_Code_TwoByte.class)); 19 | gb.registerTypeAdapter(Code.ThreeByte.class, AutoParcelAdapter.create(AutoParcel_Dtc_Code_ThreeByte.class)); 20 | gb.registerTypeAdapter(Code.WRAPPED_TYPE, Wrapped.Adapter.create(Code.class, "code")); 21 | gb.registerTypeAdapter(Code.PAGE_TYPE, Page.Adapter.create(Code.PAGE_TYPE, Code.class, "codes")); 22 | gb.registerTypeAdapter( 23 | TIME_SERIES_TYPE, TimeSeries.Adapter.create(TIME_SERIES_TYPE, Dtc.class, "codes")); 24 | } 25 | 26 | public abstract String start(); 27 | public abstract String stop(); 28 | public abstract String number(); 29 | public abstract String vehicleId(); 30 | public abstract String deviceId(); 31 | public abstract String description(); 32 | 33 | public Observable device() { 34 | return Vinli.curApp().device(deviceId()); 35 | } 36 | 37 | public Observable vehicle() { 38 | return Vinli.curApp().vehicle(vehicleId()); 39 | } 40 | 41 | public Observable diagnose() { 42 | return Vinli.curApp().diagnoseDtcCode(number()); 43 | } 44 | 45 | /*package*/ abstract Links links(); 46 | 47 | /*package*/ Dtc() { } 48 | 49 | @AutoParcel 50 | /*package*/ static abstract class Links implements Parcelable { 51 | public abstract String self(); 52 | public abstract String device(); 53 | public abstract String vehicle(); 54 | 55 | /*package*/ Links() { } 56 | } 57 | 58 | @AutoParcel 59 | public static abstract class Code implements VinliItem { 60 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { }.getType(); 61 | /*package*/ static final Type PAGE_TYPE = new TypeToken>() { }.getType(); 62 | 63 | public abstract String make(); 64 | 65 | /*package*/ Code() { } 66 | 67 | @AutoParcel 68 | /*package*/ static abstract class TwoByte implements Parcelable { 69 | public abstract String number(); 70 | public abstract String description(); 71 | 72 | /*package*/ TwoByte() { } 73 | } 74 | 75 | @AutoParcel 76 | /*package*/ static abstract class ThreeByte implements Parcelable { 77 | public abstract String number(); 78 | public abstract String ftb(); 79 | public abstract String fault(); 80 | public abstract String description(); 81 | 82 | /*package*/ ThreeByte() { } 83 | } 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Duktaper.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import java.lang.reflect.Method; 5 | import rx.exceptions.Exceptions; 6 | 7 | /** 8 | * Wrap around Duktape JS interpreter using dynamic class loading and reflection to avoid potential 9 | * issues with the the native libs not loading properly on some devices. Also, this way, if devs 10 | * choose to they can manually exclude the somewhat bloated Duktape deps from gradle and our SDK 11 | * won't blow up - it will gracefully fail, as long as exceptions around creation are caught. 12 | */ 13 | final class Duktaper { 14 | 15 | @NonNull 16 | public static Duktaper create() { 17 | try { 18 | Class duktapeCls = Class.forName("com.squareup.duktape.Duktape"); 19 | if (duktapeCls == null) throw new NullPointerException(); 20 | Object duktapeInst = duktapeCls.getDeclaredMethod("create").invoke(null); 21 | if (duktapeInst == null) throw new NullPointerException(); 22 | return new Duktaper(duktapeCls, duktapeInst); 23 | } catch (UnsatisfiedLinkError ule) { 24 | throw Exceptions.propagate(new RuntimeException("cannot link Duktape lib naturally.")); 25 | } catch (Exception e) { 26 | throw Exceptions.propagate(e); 27 | } 28 | } 29 | 30 | @NonNull private final Class duktapeCls; 31 | @NonNull private final Object duktapeInst; 32 | 33 | // cache reflective method lookups that might be called frequently 34 | private volatile Method evaluateStrStr; 35 | private volatile Method evaluateStr; 36 | 37 | private Duktaper(@NonNull Class duktapeCls, @NonNull Object duktapeInst) { 38 | this.duktapeCls = duktapeCls; 39 | this.duktapeInst = duktapeInst; 40 | } 41 | 42 | public String evaluate(String script, String fileName) { 43 | try { 44 | return (String) evaluateStrStr().invoke(duktapeInst, script, fileName); 45 | } catch (Exception e) { 46 | throw Exceptions.propagate(e); 47 | } 48 | } 49 | 50 | public String evaluate(String script) { 51 | try { 52 | return (String) evaluateStr().invoke(duktapeInst, script); 53 | } catch (Exception e) { 54 | throw Exceptions.propagate(e); 55 | } 56 | } 57 | 58 | public void close() { 59 | try { 60 | duktapeCls.getDeclaredMethod("close").invoke(duktapeInst); 61 | } catch (Exception e) { 62 | throw Exceptions.propagate(e); 63 | } 64 | } 65 | 66 | // lazy init with DCL: 67 | 68 | private Method evaluateStrStr() throws NoSuchMethodException { 69 | Method result = evaluateStrStr; 70 | if (result == null) { 71 | synchronized (this) { 72 | result = evaluateStrStr; 73 | if (result == null) { 74 | evaluateStrStr = result = // 75 | duktapeCls.getDeclaredMethod("evaluate", String.class, String.class); 76 | } 77 | } 78 | } 79 | return result; 80 | } 81 | 82 | private Method evaluateStr() throws NoSuchMethodException { 83 | Method result = evaluateStr; 84 | if (result == null) { 85 | synchronized (this) { 86 | result = evaluateStr; 87 | if (result == null) { 88 | evaluateStr = result = // 89 | duktapeCls.getDeclaredMethod("evaluate", String.class); 90 | } 91 | } 92 | } 93 | return result; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /android-net/src/androidTest/java/li/vin/net/InstTestHelper.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import java.security.cert.CertificateException; 4 | 5 | import javax.net.ssl.HostnameVerifier; 6 | import javax.net.ssl.SSLContext; 7 | import javax.net.ssl.SSLSession; 8 | import javax.net.ssl.SSLSocketFactory; 9 | import javax.net.ssl.TrustManager; 10 | import javax.net.ssl.X509TrustManager; 11 | 12 | import okhttp3.OkHttpClient; 13 | 14 | /** 15 | * Created by JoshBeridon on 12/2/16. 16 | */ 17 | 18 | public class InstTestHelper { 19 | private static VinliApp vinliApp; 20 | 21 | public static VinliApp getVinliApp() { 22 | 23 | if (vinliApp == null) { 24 | VinliApp.clientBuilder = generateUnsafeBuilder(); 25 | 26 | VinliEndpoint.setDomain(VinliEndpoint.DOMAIN_DEV); 27 | 28 | vinliApp = new VinliApp(getAccessToken()); 29 | Vinli.setCurrentApp(vinliApp); 30 | } 31 | 32 | return vinliApp; 33 | } 34 | 35 | public static String getAccessToken() { 36 | String accessToken = BuildConfig.ACCESS_TOKEN; 37 | return accessToken.equals("DEFAULT_ACCESS_TOKEN") ? null : accessToken; 38 | } 39 | 40 | public static String getDummyId() { 41 | String dummyId = BuildConfig.DUMMY_ID; 42 | return dummyId.equals("DEFAULT_DUMMY_ID") ? null : dummyId; 43 | } 44 | 45 | public static String getRouteId() { 46 | String routeId = BuildConfig.ROUTE_ID; 47 | return routeId.equals("DEFAULT_ROUTE_ID") ? null : routeId; 48 | } 49 | 50 | public static String getVIN() { 51 | String vin = BuildConfig.VIN; 52 | return vin.equals("DEFAULT_VIN") ? null : vin; 53 | } 54 | 55 | public static OkHttpClient.Builder generateUnsafeBuilder() { 56 | try { 57 | // Create a trust manager that does not validate certificate chains 58 | 59 | final X509TrustManager x509TrustManager = new X509TrustManager() { 60 | @Override 61 | public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) 62 | throws CertificateException { 63 | } 64 | 65 | @Override 66 | public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) 67 | throws CertificateException { 68 | } 69 | 70 | @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { 71 | return new java.security.cert.X509Certificate[] {}; 72 | } 73 | }; 74 | 75 | final TrustManager[] trustAllCerts = new TrustManager[] { 76 | x509TrustManager 77 | }; 78 | 79 | // Install the all-trusting trust manager 80 | final SSLContext sslContext = SSLContext.getInstance("TLS"); 81 | sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); 82 | // Create an ssl socket factory with our all-trusting manager 83 | final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); 84 | 85 | final HostnameVerifier hostnameVerifier = new HostnameVerifier() { 86 | @Override public boolean verify(final String hostname, final SSLSession session) { 87 | return true; 88 | } 89 | }; 90 | 91 | return new OkHttpClient.Builder().sslSocketFactory(sslSocketFactory, x509TrustManager) 92 | .hostnameVerifier(hostnameVerifier); 93 | } catch (Exception e) { 94 | throw new RuntimeException(e); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Collision.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.reflect.TypeToken; 8 | 9 | import java.lang.reflect.Type; 10 | 11 | import auto.parcel.AutoParcel; 12 | import java.util.Date; 13 | import rx.Observable; 14 | 15 | @AutoParcel 16 | public abstract class Collision implements VinliItem{ 17 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { }.getType(); 18 | /*package*/ static final Type TIME_SERIES_TYPE = new TypeToken>() { }.getType(); 19 | 20 | /*package*/ static final void registerGson(GsonBuilder gb) { 21 | gb.registerTypeAdapter(Collision.class, AutoParcelAdapter.create(AutoParcel_Collision.class)); 22 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(Collision.class)); 23 | gb.registerTypeAdapter(TIME_SERIES_TYPE, TimeSeries.Adapter.create(TIME_SERIES_TYPE, Collision.class)); 24 | } 25 | 26 | @NonNull public abstract String deviceId(); 27 | @NonNull public abstract String vehicleId(); 28 | @NonNull public abstract String timestamp(); 29 | @Nullable public abstract Location location(); 30 | 31 | public static Observable collisionWithId(@NonNull String collisionId) { 32 | return Vinli.curApp().collision(collisionId); 33 | } 34 | 35 | public static Observable> collisionsWithDeviceId(@NonNull String deviceId) { 36 | return collisionsWithDeviceId(deviceId, (Long) null, null, null, null); 37 | } 38 | 39 | @Deprecated 40 | public static Observable> collisionsWithDeviceId(@NonNull String deviceId, 41 | @Nullable Date since, @Nullable Date until, @Nullable Integer limit, 42 | @Nullable String sortDir) { 43 | Long sinceMs = since == null ? null : since.getTime(); 44 | Long untilMs = until == null ? null : until.getTime(); 45 | return Vinli.curApp() 46 | .collisions() 47 | .collisionsForDevice(deviceId, sinceMs, untilMs, limit, sortDir); 48 | } 49 | 50 | public static Observable> collisionsWithDeviceId(@NonNull String deviceId, 51 | @Nullable Long sinceMs, @Nullable Long untilMs, @Nullable Integer limit, 52 | @Nullable String sortDir) { 53 | return Vinli.curApp().collisions().collisionsForDevice(deviceId, sinceMs, untilMs, limit, sortDir); 54 | } 55 | 56 | public static Observable> collisionsWithVehicleId( 57 | @NonNull String vehicleId) { 58 | return collisionsWithVehicleId(vehicleId, (Long) null, null, null, null); 59 | } 60 | 61 | @Deprecated 62 | public static Observable> collisionsWithVehicleId(@NonNull String vehicleId, 63 | @Nullable Date since, @Nullable Date until, @Nullable Integer limit, 64 | @Nullable String sortDir) { 65 | Long sinceMs = since == null ? null : since.getTime(); 66 | Long untilMs = until == null ? null : until.getTime(); 67 | return collisionsWithVehicleId(vehicleId, sinceMs, untilMs, limit, sortDir); 68 | } 69 | 70 | public static Observable> collisionsWithVehicleId(@NonNull String vehicleId, 71 | @Nullable Long sinceMs, @Nullable Long untilMs, @Nullable Integer limit, 72 | @Nullable String sortDir) { 73 | return Vinli.curApp() 74 | .collisions() 75 | .collisionsForVehicle(vehicleId, sinceMs, untilMs, limit, sortDir); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/utils/PageAdapter.java: -------------------------------------------------------------------------------- 1 | package li.vin.net.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.util.Log; 5 | import android.widget.BaseAdapter; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import li.vin.net.Page; 9 | import li.vin.net.VinliItem; 10 | import rx.Observable; 11 | import rx.Subscriber; 12 | import rx.Subscription; 13 | import rx.android.schedulers.AndroidSchedulers; 14 | import rx.subscriptions.Subscriptions; 15 | 16 | public abstract class PageAdapter extends BaseAdapter { 17 | private final List> pages = new ArrayList<>(); 18 | private final Subscriber> subscriber = new Subscriber>() { 19 | @Override public void onCompleted() { } 20 | 21 | @Override public void onError(Throwable e) { 22 | PageAdapter.this.onPageError(e); 23 | } 24 | 25 | @Override public void onNext(Page page) { 26 | PageAdapter.this.onPageLoaded(page); 27 | pages.add(page); 28 | count += page.size(); 29 | PageAdapter.this.notifyDataSetChanged(); 30 | } 31 | }; 32 | 33 | private int count = 0; 34 | 35 | protected void onPageLoaded(Page page) { } 36 | 37 | protected void onPageError(Throwable e) { 38 | Log.e(this.getClass().getSimpleName(), "onPageError", e); 39 | } 40 | 41 | @Override public int getCount() { 42 | return count; 43 | } 44 | 45 | @Override public T getItem(int position) { 46 | if (position < 0 || position >= count) { 47 | throw new IllegalArgumentException(position + " is outside of the acceptable range 0.." + count); 48 | } 49 | 50 | for (int i = 0, il = pages.size(), pos = 0; i < il; ++i) { 51 | final Page page = pages.get(i); 52 | final int nextPos = pos + page.size(); 53 | if (nextPos <= position) { 54 | pos = nextPos; 55 | continue; 56 | } 57 | 58 | final int itemPos = position - pos; 59 | return page.getItems().get(itemPos); 60 | } 61 | 62 | throw new AssertionError("should never get here"); 63 | } 64 | 65 | @Override public long getItemId(int position) { 66 | return getItem(position).id().hashCode(); 67 | } 68 | 69 | public boolean hasNext() { 70 | return pages.isEmpty() || pages.get(pages.size() - 1).hasNextPage(); 71 | } 72 | 73 | public Subscription subscribe(@NonNull Observable> observable) { 74 | return subscribe(null, observable); 75 | } 76 | 77 | /** Use {@link #subscribe(Observable)} instead. */ 78 | @Deprecated 79 | public Subscription subscribe(Object context, @NonNull Observable> observable) { 80 | if (!pages.isEmpty()) { 81 | pages.clear(); 82 | count = 0; 83 | this.notifyDataSetChanged(); 84 | } 85 | 86 | return bind(observable).subscribe(subscriber); 87 | } 88 | 89 | public Subscription loadNext() { 90 | return loadNext(null); 91 | } 92 | 93 | /** Use {@link #loadNext()} instead. */ 94 | @Deprecated 95 | public Subscription loadNext(Object context) { 96 | if (hasNext()) { 97 | return bind(pages.get(pages.size() - 1).loadNextPage()).subscribe(subscriber); 98 | } else { 99 | return Subscriptions.empty(); 100 | } 101 | } 102 | 103 | private static Observable> bind(@NonNull Observable> observable) { 104 | return observable.observeOn(AndroidSchedulers.mainThread()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /android-net/src/androidTest/java/li/vin/net/StreamingInstTest.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.test.filters.SmallTest; 4 | import android.support.test.runner.AndroidJUnit4; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import rx.Subscriber; 10 | import rx.android.schedulers.AndroidSchedulers; 11 | 12 | import static junit.framework.Assert.assertTrue; 13 | 14 | /** 15 | * Created by JoshBeridon on 12/2/16. 16 | */ 17 | 18 | @RunWith(AndroidJUnit4.class) @SmallTest public class StreamingInstTest { 19 | 20 | public VinliApp vinliApp; 21 | 22 | @Before public void setup() { 23 | assertTrue(InstTestHelper.getAccessToken() != null); 24 | 25 | vinliApp = InstTestHelper.getVinliApp(); 26 | } 27 | 28 | @Test public void streamTest() { 29 | final AtomicInteger messages = new AtomicInteger(0); 30 | final String deviceId; 31 | deviceId = 32 | vinliApp.dummies().dummies(null, null).toBlocking().first().getItems().get(0).deviceId(); 33 | System.out.println(deviceId); 34 | vinliApp.dummies() 35 | .currentRun(InstTestHelper.getDummyId()) 36 | .toBlocking() 37 | .subscribe(new Subscriber>() { 38 | @Override public void onCompleted() { 39 | 40 | } 41 | 42 | @Override public void onError(Throwable e) { 43 | System.out.println("Error: " + e.getMessage()); 44 | e.printStackTrace(); 45 | assertTrue(false); 46 | } 47 | 48 | @Override public void onNext(Wrapped runWrapped) { 49 | if (runWrapped.item() != null) { 50 | vinliApp.dummies() 51 | .deleteRun(InstTestHelper.getDummyId()) 52 | .toBlocking() 53 | .subscribe(new Subscriber() { 54 | @Override public void onCompleted() { 55 | } 56 | 57 | @Override public void onError(Throwable e) { 58 | System.out.println("Error: " + e.getMessage()); 59 | e.printStackTrace(); 60 | assertTrue(false); 61 | } 62 | 63 | @Override public void onNext(Void aVoid) { 64 | 65 | } 66 | }); 67 | } 68 | } 69 | }); 70 | 71 | Dummy.Run.create() 72 | .routeId(InstTestHelper.getRouteId()) 73 | .vin(InstTestHelper.getVIN()) 74 | .save(InstTestHelper.getDummyId()) 75 | .toBlocking() 76 | .subscribe(new Subscriber() { 77 | @Override public void onCompleted() { 78 | } 79 | 80 | @Override public void onError(Throwable e) { 81 | System.out.println("Error: " + e.getMessage()); 82 | e.printStackTrace(); 83 | assertTrue(false); 84 | } 85 | 86 | @Override public void onNext(Dummy.Run run) { 87 | System.out.println(run.status().state()); 88 | } 89 | }); 90 | 91 | vinliApp.device(deviceId).toBlocking().subscribe(new Subscriber() { 92 | @Override public void onCompleted() { 93 | } 94 | 95 | @Override public void onError(Throwable e) { 96 | System.out.println("Error: " + e.getMessage()); 97 | e.printStackTrace(); 98 | assertTrue(false); 99 | } 100 | 101 | @Override public void onNext(Device device) { 102 | System.out.println(device.id()); 103 | device.stream() 104 | .observeOn(AndroidSchedulers.mainThread()) 105 | .toBlocking() 106 | .subscribe(new Subscriber() { 107 | @Override public void onCompleted() { 108 | 109 | } 110 | 111 | @Override public void onError(Throwable e) { 112 | System.out.println("Error: " + e.getMessage()); 113 | e.printStackTrace(); 114 | assertTrue(false); 115 | } 116 | 117 | @Override public void onNext(StreamMessage streamMessage) { 118 | if (messages.get() > 5) { 119 | unsubscribe(); 120 | } 121 | messages.addAndGet(1); 122 | System.out.println(streamMessage.toString()); 123 | assertTrue(streamMessage.getType() != null); 124 | } 125 | }); 126 | } 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Event.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.google.gson.GsonBuilder; 8 | import com.google.gson.reflect.TypeToken; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | import auto.parcel.AutoParcel; 13 | import java.util.Date; 14 | import rx.Observable; 15 | 16 | @AutoParcel 17 | public abstract class Event implements VinliItem { 18 | /*package*/ static final Type TIME_SERIES_TYPE = new TypeToken>() { }.getType(); 19 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { }.getType(); 20 | 21 | /*package*/ static final void registerGson(GsonBuilder gb) { 22 | gb.registerTypeAdapter(Event.class, AutoParcelAdapter.create(AutoParcel_Event.class)); 23 | gb.registerTypeAdapter(Links.class, AutoParcelAdapter.create(AutoParcel_Event_Links.class)); 24 | gb.registerTypeAdapter(Meta.class, AutoParcelAdapter.create(AutoParcel_Event_Meta.class)); 25 | gb.registerTypeAdapter(TIME_SERIES_TYPE, TimeSeries.Adapter.create(TIME_SERIES_TYPE, Event.class)); 26 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(Event.class)); 27 | } 28 | 29 | public static Observable eventWithId(@NonNull String eventId){ 30 | return Vinli.curApp().event(eventId); 31 | } 32 | 33 | public static Observable> eventsWithDeviceId(@NonNull String deviceId){ 34 | return eventsWithDeviceId(deviceId, null, null, (Long) null, null, null, null); 35 | } 36 | 37 | 38 | public static Observable> eventsWithVehicleId(@NonNull String vehicleId) { 39 | return Vinli.curApp() 40 | .events() 41 | .vehicleEvents(vehicleId, null, null, null, null, null, null); 42 | } 43 | 44 | 45 | public static Observable> eventsWithDeviceId(@NonNull String deviceId, 46 | @Nullable String type, @Nullable String objectId, @Nullable Long sinceMs, 47 | @Nullable Long untilMs, @Nullable Integer limit, @Nullable String sortDir) { 48 | return Vinli.curApp() 49 | .events() 50 | .events(deviceId, type, objectId, sinceMs, untilMs, limit, sortDir); 51 | } 52 | 53 | public static Observable> eventsWithVehicleId(@NonNull String vehicleId, 54 | @Nullable String type, @Nullable String objectId, @Nullable Long sinceMs, 55 | @Nullable Long untilMs, @Nullable Integer limit, @Nullable String sortDir) { 56 | return Vinli.curApp() 57 | .events() 58 | .vehicleEvents(vehicleId, type, objectId, sinceMs, untilMs, limit, sortDir); 59 | } 60 | 61 | @Deprecated 62 | public static Observable> eventsWithDeviceId(@NonNull String deviceId, 63 | @Nullable String type, @Nullable String objectId, @Nullable Date since, @Nullable Date until, 64 | @Nullable Integer limit, @Nullable String sortDir) { 65 | Long sinceMs = since == null ? null : since.getTime(); 66 | Long untilMs = until == null ? null : until.getTime(); 67 | return Vinli.curApp() 68 | .events() 69 | .events(deviceId, type, objectId, sinceMs, untilMs, limit, sortDir); 70 | } 71 | 72 | public abstract String eventType(); 73 | public abstract String timestamp(); 74 | public abstract String deviceId(); 75 | public abstract Meta meta(); 76 | @Nullable public abstract ObjectRef object(); 77 | 78 | public Observable> notifications() { 79 | return notifications(null, null, null, null); 80 | } 81 | 82 | public Observable> notifications(@Nullable Long sinceMs, 83 | @Nullable Long untilMs, @Nullable Integer limit, @Nullable String sortDir) { 84 | return Vinli.curApp() 85 | .notifications() 86 | .notificationsForEvent(this.id(), sinceMs, untilMs, limit, sortDir); 87 | } 88 | 89 | /*package*/ abstract Links links(); 90 | 91 | @AutoParcel 92 | /*package*/ static abstract class Links implements Parcelable { 93 | public abstract String self(); 94 | // public abstract String rules(); 95 | // public abstract String vehicles(); 96 | // public abstract String latestVehicle(); 97 | public abstract String notifications(); 98 | 99 | /*package*/ Links() { } 100 | } 101 | 102 | @AutoParcel 103 | public static abstract class Meta implements Parcelable { 104 | public abstract String direction(); 105 | public abstract boolean firstEval(); 106 | public abstract Rule rule(); 107 | public abstract Message message(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/BearingCalculator.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import java.text.DateFormat; 4 | import java.text.ParseException; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | import java.util.LinkedList; 8 | import java.util.Locale; 9 | 10 | import li.vin.net.Coordinate; 11 | 12 | /** 13 | * Created by tommy on 6/7/16. 14 | */ 15 | /*package*/ class BearingCalculator { 16 | 17 | private static final double DISTANCE_THRESHOLD = 0.00025; 18 | 19 | private BearingFilter bearingFilter; 20 | private Coordinate previousLatLng; 21 | 22 | public BearingCalculator(){ 23 | bearingFilter = new BearingFilter(); 24 | previousLatLng = null; 25 | } 26 | 27 | public void addCoordinate(Coordinate coordinate, String timestamp){ 28 | if(previousLatLng == null){ 29 | previousLatLng = coordinate; 30 | return; 31 | } 32 | 33 | double distance = Math.sqrt(Math.pow(coordinate.lat() - previousLatLng.lat(), 2) + Math.pow(coordinate.lon()- previousLatLng.lon(), 2)); 34 | if(distance > DISTANCE_THRESHOLD){ 35 | calcBearing(timestamp, coordinate, previousLatLng); 36 | previousLatLng = coordinate; 37 | } 38 | } 39 | 40 | private void calcBearing(String newTimestamp, Coordinate newCoord, Coordinate prevCoord){ 41 | double dLat = newCoord.lat() - prevCoord.lat(); 42 | double dLon = newCoord.lon() - prevCoord.lon(); 43 | 44 | double bearing = Math.atan2(Math.abs(dLat), Math.abs(dLon)); 45 | bearing = Math.toDegrees(bearing); 46 | 47 | if(dLat > 0 && dLon == 0.0){ 48 | bearing = 0.0; 49 | }else if(dLat == 0.0 && dLon > 0.0){ 50 | bearing = 90.0; 51 | }else if(dLat < 0.0 && dLon == 0){ 52 | bearing = 180; 53 | }else if(dLat == 0.0 && dLon < 0.0){ 54 | bearing = 270; 55 | }else if(dLat > 0.0 && dLon > 0.0){ 56 | bearing = 90.0 - bearing; 57 | }else if(dLat > 0.0 && dLon < 0.0){ 58 | bearing = bearing + 270; 59 | }else if(dLat < 0.0 && dLon > 0.0){ 60 | bearing = bearing + 90; 61 | }else if(dLat < 0.0 && dLon < 0.0){ 62 | bearing = 180 + (90 - bearing); 63 | } 64 | 65 | bearingFilter.addBearing(bearing, posixFromISO(newTimestamp)); 66 | } 67 | 68 | public double currentBearing(){ 69 | return bearingFilter.getFilteredBearing(); 70 | } 71 | 72 | private static long posixFromISO(String isoDate){ 73 | DateFormat fromFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()); 74 | Date date = null; 75 | 76 | try { 77 | date = fromFormat.parse(isoDate); 78 | } catch (ParseException e) { 79 | e.printStackTrace(); 80 | return 0; 81 | } 82 | 83 | return date.getTime(); 84 | } 85 | 86 | private static class BearingFilter { 87 | 88 | private static final int SIZE = 32; 89 | private static final long SHORTENED_TIME = 4000; 90 | 91 | private LinkedList bearingList; 92 | 93 | public BearingFilter(){ 94 | bearingList = new LinkedList<>(); 95 | } 96 | 97 | public void addBearing(double bearing, long posixTimestamp){ 98 | bearingList.addLast(new Bearing(bearing, posixTimestamp)); 99 | if(bearingList.size() > SIZE){ 100 | bearingList.removeFirst(); 101 | } 102 | } 103 | 104 | public double getFilteredBearing(){ 105 | if(bearingList.size() == 0){ 106 | return 0.0; 107 | } 108 | 109 | LinkedList recentBearings = new LinkedList<>(); 110 | Bearing latestBearing = bearingList.getLast(); 111 | for(Bearing bearing : bearingList){ 112 | if((latestBearing.timestamp - bearing.timestamp) <= SHORTENED_TIME){ 113 | recentBearings.addLast(bearing); 114 | } 115 | } 116 | 117 | double x = 0; 118 | double y = 0; 119 | Bearing previous = null; 120 | 121 | for(Bearing bearing : recentBearings){ 122 | long timestampDiff = bearing.timestamp - recentBearings.getFirst().timestamp; 123 | if(timestampDiff == 0){ 124 | timestampDiff = 1; 125 | } 126 | 127 | x += Math.cos(Math.toRadians(bearing.bearing)) * ((previous == null) ? 1 : timestampDiff); 128 | y += Math.sin(Math.toRadians(bearing.bearing)) * ((previous == null) ? 1 : timestampDiff); 129 | 130 | previous = bearing; 131 | } 132 | 133 | return Math.toDegrees(Math.atan2(y, x)); 134 | } 135 | 136 | public static class Bearing{ 137 | public double bearing; 138 | public long timestamp; 139 | 140 | public Bearing(double bearing, long timestamp){ 141 | this.bearing = bearing; 142 | this.timestamp = timestamp; 143 | } 144 | } 145 | 146 | } 147 | 148 | } -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Vinli.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.app.Activity; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import android.support.annotation.NonNull; 10 | import android.support.annotation.Nullable; 11 | import android.webkit.CookieManager; 12 | import android.webkit.CookieSyncManager; 13 | 14 | import rx.Observable; 15 | import rx.Observer; 16 | 17 | public final class Vinli { 18 | /*protected*/ static final String VINLI_PREFS = "li.vin.net.Vinli"; 19 | /*protected*/ static final String SIGN_IN_ERROR = "li.vin.net.Vinli#SIGN_IN_ERROR"; 20 | /*protected*/ static final String ACCESS_TOKEN = "li.vin.net.Vinli#ACCESS_TOKEN"; 21 | 22 | @SuppressWarnings("deprecation") 23 | public static void signIn(@NonNull Activity context, @NonNull String clientId, 24 | @NonNull String redirectUri, @NonNull PendingIntent pendingIntent) { 25 | 26 | CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(context); 27 | CookieManager cookieManager = CookieManager.getInstance(); 28 | cookieManager.removeAllCookie(); 29 | if (Build.VERSION.SDK_INT >= 21) cookieManager.removeAllCookies(null); 30 | cookieSyncManager.sync(); 31 | if (Build.VERSION.SDK_INT >= 21) cookieManager.flush(); 32 | 33 | context.startActivity(SignInActivity.newIntent(context, clientId, redirectUri, pendingIntent)); 34 | } 35 | 36 | public static @Nullable String getSignInError(@NonNull Intent intent) { 37 | final Bundle extras = intent.getExtras(); 38 | if (extras == null) { 39 | return null; 40 | } 41 | 42 | return extras.getString(SIGN_IN_ERROR); 43 | } 44 | 45 | private static VinliApp sApp = null; 46 | 47 | /*package*/ static void setCurrentApp(@NonNull VinliApp vinliApp){ 48 | sApp = vinliApp; 49 | } 50 | 51 | public static void clearApp(@NonNull Context context) { 52 | context.getSharedPreferences(VINLI_PREFS, Context.MODE_PRIVATE) 53 | .edit() 54 | .putString(ACCESS_TOKEN, null) 55 | .apply(); 56 | sApp = null; 57 | } 58 | 59 | public static @Nullable VinliApp initApp(Context context, @NonNull Intent intent) { 60 | if (sApp == null) { 61 | final Bundle extras = intent.getExtras(); 62 | if (extras != null) { 63 | String accessToken = extras.getString("li.vin.my.access_token"); 64 | if (accessToken == null) { 65 | // fallback for backwards compat 66 | accessToken = extras.getString(ACCESS_TOKEN); 67 | } 68 | if (accessToken != null) { 69 | context.getSharedPreferences(VINLI_PREFS, Context.MODE_PRIVATE) 70 | .edit() 71 | .putString(ACCESS_TOKEN, accessToken) 72 | .apply(); 73 | 74 | sApp = new VinliApp(accessToken); 75 | } 76 | } 77 | } 78 | 79 | return loadApp(context); 80 | } 81 | 82 | public static @Nullable VinliApp initApp(Context context, @NonNull String accessToken) { 83 | if (sApp == null) { 84 | context.getSharedPreferences(VINLI_PREFS, Context.MODE_PRIVATE) 85 | .edit() 86 | .putString(ACCESS_TOKEN, accessToken) 87 | .apply(); 88 | 89 | sApp = new VinliApp(accessToken); 90 | } 91 | 92 | return loadApp(context); 93 | } 94 | 95 | public static @Nullable VinliApp loadApp(@NonNull Context context) { 96 | if (sApp == null) { 97 | final String accessToken = context 98 | .getSharedPreferences(VINLI_PREFS, Context.MODE_PRIVATE) 99 | .getString(ACCESS_TOKEN, null); 100 | 101 | if (accessToken != null) { 102 | sApp = new VinliApp(accessToken); 103 | } 104 | } 105 | 106 | return sApp; 107 | } 108 | 109 | /*package*/ static VinliApp curApp() { 110 | if (sApp == null) { 111 | throw new IllegalStateException("no current app exists"); 112 | } 113 | return sApp; 114 | } 115 | 116 | public static void registerObserver(@NonNull Observer observer, 117 | @NonNull Observable observable) { 118 | ObserverManager.registerObserver(observer, observable); 119 | } 120 | 121 | public static void unregisterObserver(@NonNull Observer observer) { 122 | ObserverManager.unregisterObserver(observer); 123 | } 124 | 125 | private static void setEnvironmentToDev() { 126 | VinliEndpoint.setDomain(VinliEndpoint.DOMAIN_DEV); 127 | } 128 | 129 | private static void setEnvironmentToDemo() { 130 | VinliEndpoint.setDomain(VinliEndpoint.DOMAIN_DEMO); 131 | } 132 | 133 | private static void setEnvironmentToQA() { 134 | VinliEndpoint.setDomain(VinliEndpoint.DOMAIN_QA); 135 | } 136 | 137 | private Vinli() { } 138 | } 139 | -------------------------------------------------------------------------------- /gradle/bintray-upload.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | android { 5 | compileSdkVersion 24 6 | buildToolsVersion '24.0.2' 7 | } 8 | task sourcesJar(type: Jar) { 9 | from android.sourceSets.main.java.srcDirs 10 | classifier = 'sources' 11 | } 12 | 13 | task javadoc(type: Javadoc) { 14 | def variants = android.hasProperty('applicationVariants') \ 15 | ? android.applicationVariants \ 16 | : android.libraryVariants 17 | 18 | source = variants.release.javaCompile.source 19 | classpath = files(variants.release.javaCompile.classpath.files, android.bootClasspath) 20 | 21 | options.links("https://docs.oracle.com/javase/7/docs/api/"); 22 | options.linksOffline("http://d.android.com/reference", "${android.sdkDirectory}/docs/reference"); 23 | exclude '**/BuildConfig.java' 24 | exclude '**/R.java' 25 | } 26 | 27 | task javadocJar(type: Jar, dependsOn: javadoc) { 28 | classifier = 'javadoc' 29 | from javadoc.destinationDir 30 | } 31 | 32 | artifacts { 33 | archives javadocJar 34 | archives sourcesJar 35 | } 36 | 37 | // Maven 38 | install { 39 | repositories.mavenInstaller { 40 | pom.project { 41 | packaging project.hasProperty('vinli.packaging') \ 42 | ? project.property('vinli.packaging') \ 43 | : 'aar' 44 | groupId project.property('vinli.groupId') 45 | artifactId project.property('vinli.artifactId') 46 | version project.property('vinli.version') 47 | 48 | name project.property('vinli.name') 49 | description project.property('vinli.desc') 50 | url "https://github.com/vinli/${project.property('vinli.githubRepo')}" 51 | 52 | scm { 53 | url "https://github.com/vinli/${project.property('vinli.githubRepo')}" 54 | connection "scm:git:git://github.com/vinli/${project.property('vinli.githubRepo')}.git" 55 | developerConnection "scm:git:ssh://git@github.com/vinli/${project.property('vinli.githubRepo')}.git" 56 | tag project.property('vinli.version') 57 | } 58 | 59 | issueManagement { 60 | system 'GitHub Issues' 61 | url "https://github.com/vinli/${project.property('vinli.githubRepo')}/issues" 62 | } 63 | 64 | licenses { 65 | license { 66 | name 'MIT' 67 | url 'http://opensource.org/licenses/MIT' 68 | } 69 | } 70 | 71 | organization { 72 | name 'Vinli, Inc.' 73 | url 'https://vin.li' 74 | } 75 | 76 | developers { 77 | developer { 78 | id 'kturney' 79 | name 'Kyle Turney' 80 | } 81 | developer { 82 | id 'cmc5788' 83 | name 'Christopher Casey' 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | uploadArchives { 91 | repositories.mavenDeployer { 92 | pom.artifactId = project.property('vinli.artifactId') 93 | } 94 | } 95 | 96 | // Bintray 97 | Properties properties = new Properties() 98 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 99 | 100 | bintray { 101 | user = properties.getProperty('bintray.user') 102 | key = properties.getProperty('bintray.apiKey') 103 | 104 | configurations = ['archives'] 105 | 106 | dryRun = project.hasProperty('vinli.dryRun') \ 107 | ? project.property('vinli.dryRun') \ 108 | : false 109 | publish = project.hasProperty('vinli.publish') \ 110 | ? project.property('vinli.publish') \ 111 | : false 112 | 113 | pkg { 114 | repo = project.property('vinli.groupId') 115 | name = project.property('vinli.artifactId') 116 | userOrg = 'vinli' 117 | desc = project.property('vinli.desc') 118 | websiteUrl = "https://github.com/vinli/${project.property('vinli.githubRepo')}" 119 | vcsUrl = "git://github.com/vinli/${project.property('vinli.githubRepo')}.git" 120 | issueTrackerUrl = "https://github.com/vinli/${project.property('vinli.githubRepo')}/issues" 121 | licenses = ['MIT'] 122 | labels = ['android', 'vinli'] 123 | publicDownloadNumbers = project.hasProperty('vinli.publicDownloadNumbers') \ 124 | ? project.property('vinli.publicDownloadNumbers') \ 125 | : false 126 | version { 127 | name = project.property('vinli.version') 128 | vcsTag = project.property('vinli.version') 129 | released = new Date() 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Notification.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import com.google.gson.GsonBuilder; 6 | import com.google.gson.reflect.TypeToken; 7 | 8 | import java.lang.reflect.Type; 9 | 10 | import auto.parcel.AutoParcel; 11 | import java.util.Date; 12 | import rx.Observable; 13 | 14 | @AutoParcel public abstract class Notification implements VinliItem { 15 | /*package*/ static final Type PAGE_TYPE = new TypeToken>() { 16 | }.getType(); 17 | /*package*/ static final Type TIME_SERIES_TYPE = new TypeToken>() { 18 | }.getType(); 19 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { 20 | }.getType(); 21 | 22 | /*package*/ 23 | static final void registerGson(GsonBuilder gb) { 24 | gb.registerTypeAdapter(Notification.class, 25 | AutoParcelAdapter.create(AutoParcel_Notification.class)); 26 | gb.registerTypeAdapter(PAGE_TYPE, Page.Adapter.create(PAGE_TYPE, Notification.class)); 27 | gb.registerTypeAdapter(TIME_SERIES_TYPE, 28 | TimeSeries.Adapter.create(TIME_SERIES_TYPE, Notification.class)); 29 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(Notification.class)); 30 | 31 | gb.registerTypeAdapter(Links.class, 32 | AutoParcelAdapter.create(AutoParcel_Notification_Links.class)); 33 | } 34 | 35 | public static Observable notificationWithId(@NonNull String notificationId) { 36 | return Vinli.curApp().notification(notificationId); 37 | } 38 | 39 | public static Observable> notificationsWithSubscriptionId( 40 | @NonNull String subscriptionId) { 41 | return notificationsWithSubscriptionId(subscriptionId, (Long) null, null, null, null); 42 | } 43 | 44 | public static Observable> notificationsWithSubscriptionId( 45 | @NonNull String subscriptionId, @Nullable Long sinceMs, @Nullable Long untilMs, 46 | @Nullable Integer limit, @Nullable String sortDir) { 47 | return Vinli.curApp() 48 | .notifications() 49 | .notificationsForSubscription(subscriptionId, sinceMs, untilMs, limit, sortDir); 50 | } 51 | 52 | @Deprecated 53 | public static Observable> notificationsWithSubscriptionId( 54 | @NonNull String subscriptionId, @Nullable Date since, @Nullable Date until, 55 | @Nullable Integer limit, @Nullable String sortDir) { 56 | Long sinceMs = since == null ? null : since.getTime(); 57 | Long untilMs = until == null ? null : until.getTime(); 58 | return Vinli.curApp() 59 | .notifications() 60 | .notificationsForSubscription(subscriptionId, sinceMs, untilMs, limit, sortDir); 61 | } 62 | 63 | public static Observable> notificationsWithEventId( 64 | @NonNull String eventId) { 65 | return notificationsWithEventId(eventId, (Long) null, null, null, null); 66 | } 67 | 68 | public static Observable> notificationsWithEventId( 69 | @NonNull String eventId, @Nullable Long sinceMs, @Nullable Long untilMs, 70 | @Nullable Integer limit, @Nullable String sortDir) { 71 | return Vinli.curApp() 72 | .notifications() 73 | .notificationsForEvent(eventId, sinceMs, untilMs, limit, sortDir); 74 | } 75 | 76 | @Deprecated 77 | public static Observable> notificationsWithEventId( 78 | @NonNull String eventId, @Nullable Date since, @Nullable Date until, @Nullable Integer limit, 79 | @Nullable String sortDir) { 80 | Long sinceMs = since == null ? null : since.getTime(); 81 | Long untilMs = until == null ? null : until.getTime(); 82 | return Vinli.curApp() 83 | .notifications() 84 | .notificationsForEvent(eventId, sinceMs, untilMs, limit, sortDir); 85 | } 86 | 87 | public abstract String createdAt(); 88 | 89 | public abstract String eventId(); 90 | 91 | public abstract String eventTimestamp(); 92 | 93 | public abstract String eventType(); 94 | 95 | public abstract String notifiedAt(); 96 | 97 | public abstract String payload(); 98 | 99 | public abstract String respondedAt(); 100 | 101 | public abstract String response(); 102 | 103 | public abstract int responseCode(); 104 | 105 | public abstract String state(); 106 | 107 | public abstract String subscriptionId(); 108 | 109 | public abstract String url(); 110 | 111 | /*package*/ 112 | abstract Links links(); 113 | 114 | @AutoParcel 115 | /*package*/ static abstract class Links { 116 | public abstract String self(); 117 | 118 | public abstract String subscription(); 119 | 120 | public abstract String event(); 121 | 122 | /*package*/ Links() { 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /android-net/src/test/java/li/vin/net/DevicesIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.robolectric.RobolectricTestRunner; 7 | import org.robolectric.annotation.Config; 8 | 9 | import rx.Subscriber; 10 | 11 | import static junit.framework.Assert.assertTrue; 12 | 13 | @RunWith(RobolectricTestRunner.class) 14 | @Config(constants = BuildConfig.class, sdk = 22) 15 | public class DevicesIntegrationTests { 16 | 17 | public VinliApp vinliApp; 18 | 19 | @Before 20 | public void setup(){ 21 | assertTrue(TestHelper.getAccessToken() != null); 22 | 23 | vinliApp = TestHelper.getVinliApp(); 24 | } 25 | 26 | @Test public void getPagedDevices() { 27 | vinliApp.devices(1, 0) 28 | .toBlocking() 29 | .subscribe(new Subscriber>() { 30 | @Override public void onCompleted() { 31 | 32 | } 33 | 34 | @Override public void onError(Throwable e) { 35 | e.printStackTrace(); 36 | assertTrue(false); 37 | } 38 | 39 | @Override public void onNext(Page devicePage) { 40 | assertTrue(devicePage.getItems().size() > 0); 41 | 42 | for(Device device : devicePage.getItems()){ 43 | assertTrue(device.id() != null && device.id().length() > 0); 44 | assertTrue(device.name() != null && device.name().length() > 0); 45 | } 46 | 47 | if (devicePage.hasNextPage()) { 48 | devicePage.loadNextPage().toBlocking() 49 | .subscribe(new Subscriber>() { 50 | @Override public void onCompleted() { 51 | 52 | } 53 | 54 | @Override public void onError(Throwable e) { 55 | e.printStackTrace(); 56 | assertTrue(false); 57 | } 58 | 59 | @Override public void onNext(Page devicePage) { 60 | assertTrue(devicePage.getItems().size() > 0); 61 | 62 | for(Device device : devicePage.getItems()){ 63 | assertTrue(device.id() != null && device.id().length() > 0); 64 | assertTrue(device.name() != null && device.name().length() > 0); 65 | } 66 | } 67 | }); 68 | } 69 | } 70 | }); 71 | } 72 | 73 | @Test 74 | public void testGetDevices(){ 75 | vinliApp.devices().toBlocking().subscribe(new Subscriber>() { 76 | @Override 77 | public void onCompleted() { 78 | 79 | } 80 | 81 | @Override 82 | public void onError(Throwable e) { 83 | e.printStackTrace(); 84 | assertTrue(false); 85 | } 86 | 87 | @Override 88 | public void onNext(Page devicePage) { 89 | assertTrue(devicePage.getItems().size() > 0); 90 | 91 | for(Device device : devicePage.getItems()){ 92 | assertTrue(device.id() != null && device.id().length() > 0); 93 | assertTrue(device.name() != null && device.name().length() > 0); 94 | } 95 | } 96 | }); 97 | } 98 | 99 | @Test 100 | public void testGetDevicesByUrl(){ 101 | vinliApp.devicesSvc().devicesForUrl(String.format("%sdevices", VinliEndpoint.PLATFORM.getUrl())) 102 | .toBlocking().subscribe(new Subscriber>() { 103 | @Override 104 | public void onCompleted() { 105 | 106 | } 107 | 108 | @Override 109 | public void onError(Throwable e) { 110 | e.printStackTrace(); 111 | assertTrue(false); 112 | } 113 | 114 | @Override 115 | public void onNext(Page devicePage) { 116 | assertTrue(devicePage.getItems().size() > 0); 117 | 118 | for(Device device : devicePage.getItems()){ 119 | assertTrue(device.id() != null && device.id().length() > 0); 120 | assertTrue(device.name() != null && device.name().length() > 0); 121 | } 122 | } 123 | }); 124 | } 125 | 126 | @Test 127 | public void testGetDeviceById(){ 128 | assertTrue(TestHelper.getDeviceId() != null); 129 | 130 | Device.deviceWithId(TestHelper.getDeviceId()).toBlocking().subscribe(new Subscriber() { 131 | @Override 132 | public void onCompleted() { 133 | 134 | } 135 | 136 | @Override 137 | public void onError(Throwable e) { 138 | e.printStackTrace(); 139 | assertTrue(false); 140 | } 141 | 142 | @Override 143 | public void onNext(Device device) { 144 | assertTrue(device.id() != null && device.id().length() > 0); 145 | assertTrue(device.name() != null && device.name().length() > 0); 146 | } 147 | }); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /android-net/build.gradle: -------------------------------------------------------------------------------- 1 | def integrationPropertiesFile = file("integration.properties"); 2 | def integrationProperties = new Properties(); 3 | integrationProperties.load(new FileInputStream(integrationPropertiesFile)); 4 | 5 | buildscript { 6 | repositories { 7 | mavenCentral() 8 | jcenter() 9 | } 10 | dependencies { 11 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' 12 | } 13 | } 14 | 15 | group = project.property('vinli.groupId') 16 | version = project.property('vinli.version') 17 | 18 | apply plugin: 'com.android.library' 19 | apply from: file('../gradle/checkstyle.gradle') 20 | apply from: file('../gradle/javadoc.gradle') 21 | apply from: file('../gradle/bintray-upload.gradle') 22 | apply from: file('../gradle/mavenize.gradle') 23 | apply plugin: 'android-apt' 24 | 25 | android { 26 | compileSdkVersion 24 27 | buildToolsVersion '24.0.2' 28 | 29 | defaultConfig { 30 | minSdkVersion 14 31 | targetSdkVersion 24 32 | versionCode 1 33 | versionName "1.0" 34 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 35 | } 36 | 37 | buildTypes { 38 | debug { 39 | buildConfigField("String", "ACCESS_TOKEN", "\"" + integrationProperties['ACCESS_TOKEN'] + "\"") 40 | buildConfigField("String", "VEHICULARIZATION_ACCESS_TOKEN", "\"" + integrationProperties['VEHICULARIZATION_ACCESS_TOKEN'] + "\"") 41 | buildConfigField("String", "DEVICE_ID", "\"" + integrationProperties['DEVICE_ID'] + "\"") 42 | buildConfigField("String", "SUBSCRIPTION_ID", "\"" + integrationProperties['SUBSCRIPTION_ID'] + "\"") 43 | buildConfigField("String", "RULE_ID", "\"" + integrationProperties['RULE_ID'] + "\"") 44 | buildConfigField("String", "VEHICLE_RULE_ID", "\"" + integrationProperties['VEHICLE_RULE_ID'] + "\"") 45 | buildConfigField("String", "VEHICLE_ID", "\"" + integrationProperties['VEHICLE_ID'] + "\"") 46 | buildConfigField("String", "SECOND_VEHICLE_ID", "\"" + integrationProperties['SECOND_VEHICLE_ID'] + "\"") 47 | buildConfigField("String", "EVENT_ID", "\"" + integrationProperties['EVENT_ID'] + "\"") 48 | buildConfigField("String", "NOTIFICATION_ID", "\"" + integrationProperties['NOTIFICATION_ID'] + "\"") 49 | buildConfigField("String", "MESSAGE_ID", "\"" + integrationProperties['MESSAGE_ID'] + "\"") 50 | buildConfigField("String", "TRIP_ID", "\"" + integrationProperties['TRIP_ID'] + "\"") 51 | buildConfigField("String", "DUMMY_ID", "\"" + integrationProperties['DUMMY_ID'] + "\"") 52 | buildConfigField("String", "ROUTE_ID", "\"" + integrationProperties['ROUTE_ID'] + "\"") 53 | buildConfigField("String", "VIN", "\"" + integrationProperties['VIN'] + "\"") 54 | buildConfigField("String", "ODO_ID", "\"" + integrationProperties['ODO_ID'] + "\"") 55 | buildConfigField("String", "ODO_TRIGGER_ID", "\"" + integrationProperties['ODO_TRIGGER_ID'] + "\"") 56 | buildConfigField("String", "COLLISION_ID", "\"" + integrationProperties['COLLISION_ID'] + "\"") 57 | buildConfigField("String", "REPORT_CARD_ID", "\"" + integrationProperties['REPORT_CARD_ID'] + "\"") 58 | } 59 | release { 60 | minifyEnabled false 61 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 62 | } 63 | } 64 | 65 | compileOptions { 66 | sourceCompatibility JavaVersion.VERSION_1_7 67 | targetCompatibility JavaVersion.VERSION_1_7 68 | } 69 | 70 | lintOptions { 71 | abortOnError false 72 | } 73 | } 74 | 75 | dependencies { 76 | 77 | compile 'com.squareup.duktape:duktape-android:0.9.5' 78 | 79 | 80 | provided 'com.android.support:appcompat-v7:24.+' 81 | 82 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 83 | compile 'com.squareup.retrofit2:converter-gson:2.0.2' 84 | compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' 85 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' 86 | compile 'com.squareup.okhttp3:okhttp-ws:3.4.+' 87 | compile 'io.reactivex:rxandroid:1.0.1' 88 | compile 'io.reactivex:rxjava:1.1.+' 89 | compile 'com.google.code.gson:gson:2.+' 90 | provided 'com.android.support:support-annotations:24.2.1' 91 | compile 'com.github.frankiesardo:auto-parcel:0.3' 92 | apt 'com.github.frankiesardo:auto-parcel-processor:0.3' 93 | 94 | testCompile "org.robolectric:robolectric:3.1.2" 95 | testCompile 'org.mockito:mockito-core:1.10.19' 96 | testCompile 'junit:junit:4.12' 97 | testCompile group: 'org.robolectric', name: 'shadows-httpclient', version: '3.0' 98 | 99 | androidTestCompile 'com.android.support:support-annotations:25.0.0' 100 | androidTestCompile 'com.android.support.test:runner:0.5' 101 | androidTestCompile 'com.android.support.test:rules:0.5' 102 | androidTestCompile 'org.hamcrest:hamcrest-library:1.3' 103 | } -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/ReportCard.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import android.support.annotation.Nullable; 6 | import com.google.gson.GsonBuilder; 7 | import com.google.gson.internal.LinkedTreeMap; 8 | import com.google.gson.reflect.TypeToken; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | import auto.parcel.AutoParcel; 13 | import java.util.Date; 14 | import rx.Observable; 15 | 16 | @AutoParcel public abstract class ReportCard implements VinliItem { 17 | /*package*/ static final Type TIME_SERIES_TYPE = new TypeToken>() { 18 | }.getType(); 19 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { 20 | }.getType(); 21 | 22 | /*package*/ 23 | static final void registerGson(GsonBuilder gb) { 24 | gb.registerTypeAdapter(ReportCard.class, AutoParcelAdapter.create(AutoParcel_ReportCard.class)); 25 | gb.registerTypeAdapter(OverallReportCard.class, 26 | AutoParcelAdapter.create(AutoParcel_ReportCard_OverallReportCard.class)); 27 | gb.registerTypeAdapter(OverallReportCard.InnerReportCard.class, 28 | AutoParcelAdapter.create(AutoParcel_ReportCard_OverallReportCard_InnerReportCard.class)); 29 | gb.registerTypeAdapter(TIME_SERIES_TYPE, 30 | TimeSeries.Adapter.create(TIME_SERIES_TYPE, ReportCard.class, "reportCards")); 31 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(ReportCard.class, "reportCard")); 32 | } 33 | 34 | @NonNull public abstract String deviceId(); 35 | 36 | @NonNull public abstract String vehicleId(); 37 | 38 | @NonNull public abstract String tripId(); 39 | 40 | @NonNull public abstract String grade(); 41 | 42 | public static Observable reportCardWithId(@NonNull String reportCardId) { 43 | return Vinli.curApp().reportCard(reportCardId); 44 | } 45 | 46 | public static Observable> reportCardsWithDeviceId( 47 | @NonNull String deviceId) { 48 | return reportCardsWithDeviceId(deviceId, (Long) null, null, null, null); 49 | } 50 | 51 | public static Observable> reportCardsWithDeviceId(@NonNull String deviceId, 52 | @Nullable Long sinceMs, @Nullable Long untilMs, @Nullable Integer limit, 53 | @Nullable String sortDir) { 54 | return Vinli.curApp() 55 | .reportCards() 56 | .reportCardsForDevice(deviceId, sinceMs, untilMs, limit, sortDir); 57 | } 58 | 59 | @Deprecated 60 | public static Observable> reportCardsWithDeviceId(@NonNull String deviceId, 61 | @Nullable Date since, @Nullable Date until, @Nullable Integer limit, 62 | @Nullable String sortDir) { 63 | Long sinceMs = since == null ? null : since.getTime(); 64 | Long untilMs = until == null ? null : until.getTime(); 65 | return Vinli.curApp() 66 | .reportCards() 67 | .reportCardsForDevice(deviceId, sinceMs, untilMs, limit, sortDir); 68 | } 69 | 70 | public static Observable> reportCardsWithVehicleId( 71 | @NonNull String vehicleId) { 72 | return reportCardsWithVehicleId(vehicleId, null, null, null, null); 73 | } 74 | 75 | public static Observable> reportCardsWithVehicleId( 76 | @NonNull String vehicleId, @Nullable Long sinceMs, @Nullable Long untilMs, 77 | @Nullable Integer limit, @Nullable String sortDir) { 78 | return Vinli.curApp() 79 | .reportCards() 80 | .reportCardsForVehicle(vehicleId, sinceMs, untilMs, limit, sortDir); 81 | } 82 | 83 | @Deprecated 84 | public static Observable> reportCardWithVehicleId( 85 | @NonNull String vehicleId, @Nullable Date since, @Nullable Date until, 86 | @Nullable Integer limit, @Nullable String sortDir) { 87 | Long sinceMs = since == null ? null : since.getTime(); 88 | Long untilMs = until == null ? null : until.getTime(); 89 | return Vinli.curApp() 90 | .reportCards() 91 | .reportCardsForVehicle(vehicleId, sinceMs, untilMs, limit, sortDir); 92 | } 93 | 94 | public static Observable reportCardWithTripId(@NonNull String tripId) { 95 | return Vinli.curApp() 96 | .reportCards() 97 | .reportCardForTrip(tripId) 98 | .map(Wrapped.pluckItem()); 99 | } 100 | 101 | @AutoParcel public static abstract class OverallReportCard { 102 | 103 | @NonNull public abstract Integer tripSampleSize(); 104 | 105 | @Nullable public abstract LinkedTreeMap gradeCount(); 106 | 107 | @NonNull /*package*/ abstract InnerReportCard reportCard(); 108 | 109 | public String overallGrade() { 110 | return reportCard().overallGrade(); 111 | } 112 | 113 | @AutoParcel 114 | /*package*/ static abstract class InnerReportCard { 115 | @NonNull public abstract String overallGrade(); 116 | 117 | /*package*/ InnerReportCard() { 118 | } 119 | } 120 | 121 | public static Observable overallReportCardForDevice( 122 | @NonNull String deviceId) { 123 | return Vinli.curApp().reportCards().overallReportCardForDevice(deviceId); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Odometer.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import auto.parcel.AutoParcel; 7 | import com.google.gson.Gson; 8 | import com.google.gson.GsonBuilder; 9 | import com.google.gson.TypeAdapter; 10 | import com.google.gson.reflect.TypeToken; 11 | import com.google.gson.stream.JsonReader; 12 | import com.google.gson.stream.JsonWriter; 13 | import java.io.IOException; 14 | import java.lang.reflect.Type; 15 | import java.util.Date; 16 | import rx.Observable; 17 | 18 | @AutoParcel 19 | public abstract class Odometer implements VinliItem{ 20 | /*package*/ static final Type TIME_SERIES_TYPE = new TypeToken>() { }.getType(); 21 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { }.getType(); 22 | 23 | /*package*/ static final void registerGson(GsonBuilder gb) { 24 | gb.registerTypeAdapter(Odometer.class, AutoParcelAdapter.create(AutoParcel_Odometer.class)); 25 | gb.registerTypeAdapter(Links.class, AutoParcelAdapter.create(AutoParcel_Odometer_Links.class)); 26 | gb.registerTypeAdapter(Odometer.Seed.class, new Seed.Adapter()); 27 | 28 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(Odometer.class)); 29 | gb.registerTypeAdapter(TIME_SERIES_TYPE, TimeSeries.Adapter.create(TIME_SERIES_TYPE, Odometer.class)); 30 | } 31 | 32 | public static Observable odometerWithId(@NonNull String odometerId) { 33 | return Vinli.curApp().odometerReport(odometerId); 34 | } 35 | 36 | public static Observable> odometersWithVehicleId(@NonNull String vehicleId) { 37 | return odometersWithVehicleId(vehicleId, (Long) null, null, null, null); 38 | } 39 | 40 | public static Observable> odometersWithVehicleId(@NonNull String vehicleId, 41 | @Nullable Long sinceMs, @Nullable Long untilMs, @Nullable Integer limit, 42 | @Nullable String sortDir) { 43 | return Vinli.curApp().distances().odometerReports(vehicleId, sinceMs, untilMs, limit, sortDir); 44 | } 45 | 46 | @Deprecated 47 | public static Observable> odometersWithVehicleId(@NonNull String vehicleId, 48 | @Nullable Date since, @Nullable Date until, @Nullable Integer limit, 49 | @Nullable String sortDir) { 50 | Long sinceMs = since == null ? null : since.getTime(); 51 | Long untilMs = until == null ? null : until.getTime(); 52 | return Vinli.curApp().distances().odometerReports(vehicleId, sinceMs, untilMs, limit, sortDir); 53 | } 54 | 55 | public abstract String vehicleId(); 56 | public abstract Double reading(); 57 | public abstract String date(); 58 | 59 | /*package*/ abstract Links links(); 60 | 61 | public static final Seed.Saver create() { 62 | return new AutoParcel_Odometer_Seed.Builder(); 63 | } 64 | 65 | public Observable delete(){ 66 | return Vinli.curApp().distances().deleteOdometerReport(id()); 67 | } 68 | 69 | @AutoParcel 70 | /*package*/ static abstract class Links implements Parcelable { 71 | public abstract String vehicle(); 72 | 73 | /*package*/ Links() { } 74 | } 75 | 76 | @AutoParcel 77 | public static abstract class Seed{ 78 | @NonNull public abstract Double reading(); 79 | @Nullable public abstract String date(); 80 | @NonNull public abstract DistanceUnit unit(); 81 | @NonNull public abstract String vehicleId(); 82 | 83 | /*package*/ Seed() { } 84 | 85 | @AutoParcel.Builder 86 | public static abstract class Saver{ 87 | public abstract Saver reading(@NonNull Double reading); 88 | public abstract Saver date(@Nullable String date); 89 | public abstract Saver unit(@NonNull DistanceUnit unit); 90 | public abstract Saver vehicleId(@NonNull String vehicleId); 91 | 92 | /*package*/ Saver() {} 93 | 94 | /*package*/ abstract Seed autoBuild(); 95 | 96 | public Observable save() { 97 | final Seed s = autoBuild(); 98 | 99 | return Vinli.curApp().distances().createOdometerReport(s.vehicleId(), s) 100 | .map(Wrapped.pluckItem()); 101 | } 102 | } 103 | 104 | /*package*/ static final class Adapter extends TypeAdapter { 105 | private Gson gson; 106 | 107 | @Override public void write(JsonWriter out, Seed value) throws IOException { 108 | if (gson == null) { 109 | gson = Vinli.curApp().gson(); 110 | } 111 | 112 | out.beginObject(); 113 | out.name("odometer").beginObject(); 114 | out.name("reading").value(value.reading()); 115 | 116 | final String date = value.date(); 117 | if(date != null){ 118 | out.name("date").value(value.date()); 119 | } 120 | out.name("unit").value(value.unit().getDistanceUnitStr()); 121 | out.endObject(); 122 | out.endObject(); 123 | } 124 | 125 | @Override public Seed read(JsonReader in) throws IOException { 126 | throw new UnsupportedOperationException("reading a OdometerSeed is not supported"); 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/SignInActivity.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.app.Activity; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.support.annotation.NonNull; 10 | import android.util.Log; 11 | import android.webkit.WebView; 12 | import android.webkit.WebViewClient; 13 | 14 | import okhttp3.HttpUrl; 15 | //import com.squareup.okhttp.HttpUrl; 16 | 17 | public class SignInActivity extends Activity { 18 | private static final String TAG = SignInActivity.class.getSimpleName(); 19 | 20 | private static final String CLIENT_ID = "li.vin.net.SignInActivity#CLIENT_ID"; 21 | private static final String REDIRECT_URI = "li.vin.net.SignInActivity#REDIRECT_URI"; 22 | private static final String PENDING_INTENT = "li.vin.net.SignInActivity#PENDING_INTENT"; 23 | 24 | private static final String ACTION_ERROR = "li.vin.net.signIn.ERROR"; 25 | private static final String ACTION_APPROVED = "li.vin.net.signIn.APPROVED"; 26 | 27 | private static final HttpUrl OAUTH_ENPOINT = new HttpUrl.Builder().scheme("https") 28 | .host("auth" + VinliEndpoint.domain()) 29 | .addPathSegment("oauth") 30 | .addPathSegment("authorization") 31 | .addPathSegment("new") 32 | .addQueryParameter("response_type", "token") 33 | .build(); 34 | 35 | /*protected*/ 36 | static final Intent newIntent(@NonNull Context context, @NonNull String clientId, 37 | @NonNull String redirectUri, @NonNull PendingIntent pendingIntent) { 38 | final Intent signInIntent = new Intent(context, SignInActivity.class); 39 | 40 | signInIntent.putExtra(CLIENT_ID, clientId); 41 | signInIntent.putExtra(REDIRECT_URI, redirectUri); 42 | signInIntent.putExtra(PENDING_INTENT, pendingIntent); 43 | 44 | return signInIntent; 45 | } 46 | 47 | @Override protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | 50 | final Bundle extras = getIntent().getExtras(); 51 | if (extras == null) { 52 | throw new AssertionError("missing app info extras"); 53 | } 54 | 55 | final String clientId = extras.getString(CLIENT_ID); 56 | if (clientId == null) { 57 | throw new AssertionError("missing client ID"); 58 | } 59 | 60 | final String redirectUri = extras.getString(REDIRECT_URI); 61 | if (redirectUri == null) { 62 | throw new AssertionError("missing redirect URI"); 63 | } 64 | 65 | final PendingIntent pendingIntent = extras.getParcelable(PENDING_INTENT); 66 | if (pendingIntent == null) { 67 | throw new AssertionError("missing pending intent"); 68 | } 69 | 70 | setContentView(R.layout.activity_vinli_sign_in); 71 | 72 | final WebView wv = (WebView) this.findViewById(li.vin.net.R.id.sign_in); 73 | 74 | wv.setWebViewClient(new WebViewClient() { 75 | @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { 76 | Log.d(TAG, "shouldOverrideUrlLoading: " + url); 77 | 78 | if (url.startsWith(redirectUri)) { 79 | 80 | String error = null; 81 | String accessToken = null; 82 | 83 | try { 84 | final HttpUrl uri = HttpUrl.parse(url); 85 | final String[] fragmentPieces = uri.fragment().split("&"); 86 | for (String piece : fragmentPieces) { 87 | if (piece.startsWith("access_token=")) { 88 | accessToken = piece.substring("access_token=".length()); 89 | break; 90 | } else if (piece.startsWith("error=")) { 91 | error = piece.substring("error=".length()); 92 | break; 93 | } 94 | } 95 | } catch (Exception e) { 96 | error = "redirect parse error: " + e; 97 | } 98 | 99 | Intent resultIntent; 100 | 101 | if (error == null) { 102 | if (accessToken == null) { 103 | resultIntent = new Intent(ACTION_ERROR); 104 | resultIntent.putExtra(Vinli.SIGN_IN_ERROR, "missing access_token"); 105 | } else { 106 | Log.d(TAG, "oauth accessToken: " + accessToken); 107 | resultIntent = new Intent(ACTION_APPROVED); 108 | resultIntent.putExtra(Vinli.ACCESS_TOKEN, accessToken); 109 | } 110 | } else { 111 | Log.d(TAG, "oauth error: " + error); 112 | resultIntent = new Intent(ACTION_ERROR); 113 | resultIntent.putExtra(Vinli.SIGN_IN_ERROR, error); 114 | } 115 | 116 | try { 117 | pendingIntent.send(SignInActivity.this, 0, resultIntent); 118 | finish(); 119 | } catch (Exception e) { 120 | Log.d(TAG, "pending intent send error: " + e); 121 | } 122 | return true; 123 | } 124 | 125 | if (url.toLowerCase().contains("sign-up")) { 126 | try { 127 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 128 | } catch (Exception e) { 129 | Log.d(TAG, "failed to launch sign up url", e); 130 | } 131 | return true; 132 | } 133 | return false; 134 | } 135 | }); 136 | 137 | final String url = OAUTH_ENPOINT.newBuilder() 138 | .host("auth" + VinliEndpoint.domain()) 139 | .setQueryParameter("client_id", clientId) 140 | .setQueryParameter("redirect_uri", redirectUri) 141 | .toString(); 142 | 143 | Log.d("SignInActivity", "loading url: " + url); 144 | wv.loadUrl(url); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | # machine: 2 | # environment: 3 | # GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' 4 | 5 | # dependencies: 6 | # pre: 7 | # - git clone git@github.com:vinli/build.git .build 8 | # override: 9 | # - .build/android-net/pretest.sh 10 | 11 | # test: 12 | # pre: 13 | # - mksdcard -l e 512M mysdcard.img 14 | # - emulator -avd circleci-android22 -no-audio -no-window -sdcard mysdcard.img: 15 | # background: true 16 | # parallel: true 17 | # - circle-android wait-for-boot 18 | # override: 19 | # - .build/android-net/test.sh 20 | # post: 21 | # - .build/android-net/posttest.sh 22 | 23 | # This configuration was automatically generated from a CircleCI 1.0 config. 24 | # It should include any build commands you had along with commands that CircleCI 25 | # inferred from your project structure. We strongly recommend you read all the 26 | # comments in this file to understand the structure of CircleCI 2.0, as the idiom 27 | # for configuration has changed substantially in 2.0 to allow arbitrary jobs rather 28 | # than the prescribed lifecycle of 1.0. In general, we recommend using this generated 29 | # configuration as a reference rather than using it in production, though in most 30 | # cases it should duplicate the execution of your original 1.0 config. 31 | version: 2 32 | jobs: 33 | build: 34 | parallelism: 1 35 | shell: /bin/bash --login 36 | docker: 37 | - image: circleci/android:api-24-alpha 38 | # CircleCI 2.0 does not support environment variables that refer to each other the same way as 1.0 did. 39 | # If any of these refer to each other, rewrite them so that they don't or see https://circleci.com/docs/2.0/env-vars/#interpolating-environment-variables-to-set-other-environment-variables . 40 | environment: 41 | CIRCLE_ARTIFACTS: /tmp/circleci-artifacts 42 | CIRCLE_TEST_REPORTS: /tmp/circleci-test-results 43 | GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError" 44 | # In CircleCI 1.0 we used a pre-configured image with a large number of languages and other packages. 45 | # In CircleCI 2.0 you can now specify your own image, or use one of our pre-configured images. 46 | # The following configuration line tells CircleCI to use the specified docker image as the runtime environment for you job. 47 | # We have selected a pre-built image that mirrors the build environment we use on 48 | # the 1.0 platform, but we recommend you choose an image more tailored to the needs 49 | # of each job. For more information on choosing an image (or alternatively using a 50 | # VM instead of a container) see https://circleci.com/docs/2.0/executor-types/ 51 | # To see the list of pre-built images that CircleCI provides for most common languages see 52 | # https://circleci.com/docs/2.0/circleci-images/ 53 | 54 | steps: 55 | # Machine Setup 56 | # If you break your build into multiple jobs with workflows, you will probably want to do the parts of this that are relevant in each 57 | # The following `checkout` command checks out your code to your working directory. In 1.0 we did this implicitly. In 2.0 you can choose where in the course of a job your code should be checked out. 58 | - checkout 59 | # Prepare for artifact and test results collection equivalent to how it was done on 1.0. 60 | # In many cases you can simplify this from what is generated here. 61 | # 'See docs on artifact collection here https://circleci.com/docs/2.0/artifacts/' 62 | - run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS 63 | # Dependencies 64 | # This would typically go in either a build or a build-and-test job when using workflows 65 | # Restore the dependency cache 66 | - restore_cache: 67 | keys: 68 | # This branch if available 69 | - v1-dep-{{ .Branch }}- 70 | # Default branch if not 71 | - v1-dep-master- 72 | # Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly 73 | - v1-dep- 74 | # This is based on your 1.0 configuration file or project settings 75 | - run: git clone git@github.com:vinli/build.git .build 76 | # This is based on your 1.0 configuration file or project settings 77 | - run: .build/android-net/pretest.sh 78 | # Save dependency cache 79 | - save_cache: 80 | key: v1-dep-{{ .Branch }}-{{ epoch }} 81 | paths: 82 | # This is a broad list of cache paths to include many possible development environments 83 | # You can probably delete some of these entries 84 | - vendor/bundle 85 | - ~/virtualenvs 86 | - ~/.m2 87 | - ~/.ivy2 88 | - ~/.bundle 89 | - ~/.go_workspace 90 | - ~/.gradle 91 | - ~/.cache/bower 92 | # Test 93 | 94 | # ******** Circle 2 currently does not support emulator testing, see more at link below: ********************** 95 | # https://support.circleci.com/hc/en-us/articles/360000028928-Testing-with-Android-emulator-on-CircleCI-2-0 96 | # - run: mksdcard -l e 512M mysdcard.img 97 | # - run: 98 | # command: emulator -avd circleci-android22 -no-audio -no-window -sdcard mysdcard.img 99 | # background: true 100 | # - run: circle-android wait-for-boot 101 | # This is based on your 1.0 configuration file or project settings 102 | # - run: .build/android-net/test.sh 103 | 104 | # This is based on your 1.0 configuration file or project settings 105 | 106 | 107 | # - run: export TERM=${TERM:-dumb} && ./gradlew clean connectedAndroidTest 108 | # Teardown 109 | # If you break your build into multiple jobs with workflows, you will probably want to do the parts of this that are relevant in each 110 | # Save test results 111 | - store_test_results: 112 | path: /tmp/circleci-test-results 113 | # Save artifacts 114 | - store_artifacts: 115 | path: /tmp/circleci-artifacts 116 | - store_artifacts: 117 | path: /tmp/circleci-test-results 118 | -------------------------------------------------------------------------------- /android-net/src/test/java/li/vin/net/DiagnosticsIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.robolectric.RobolectricTestRunner; 8 | import org.robolectric.annotation.Config; 9 | 10 | import rx.Subscriber; 11 | 12 | import static junit.framework.Assert.assertTrue; 13 | 14 | @RunWith(RobolectricTestRunner.class) 15 | @Config(constants = BuildConfig.class, sdk = 22) 16 | public class DiagnosticsIntegrationTests { 17 | 18 | public VinliApp vinliApp; 19 | 20 | @Before 21 | public void setup(){ 22 | assertTrue(TestHelper.getAccessToken() != null); 23 | 24 | vinliApp = TestHelper.getVinliApp(); 25 | } 26 | 27 | @Test 28 | public void testGetPagedCodes(){ 29 | assertTrue(TestHelper.getVehicleId() != null); 30 | 31 | vinliApp.diagnostics().codes(TestHelper.getVehicleId(), null, null, 1, null,null) 32 | .toBlocking().subscribe(new Subscriber>() { 33 | @Override 34 | public void onCompleted() { 35 | } 36 | 37 | @Override 38 | public void onError(Throwable e) { 39 | System.out.println("Error: " + e.getMessage()); 40 | e.printStackTrace(); 41 | assertTrue(false); 42 | } 43 | 44 | @Override 45 | public void onNext(TimeSeries dtcTimeSeries) { 46 | assertTrue(dtcTimeSeries.getItems().size() > 0); 47 | 48 | for(Dtc dtc : dtcTimeSeries.getItems()){ 49 | assertTrue(dtc.start() != null && dtc.start().length() > 0); 50 | assertTrue(dtc.vehicleId() != null && dtc.vehicleId().length() > 0); 51 | assertTrue(dtc.deviceId() != null && dtc.deviceId().length() > 0); 52 | assertTrue(dtc.number() != null && dtc.number().length() > 0); 53 | assertTrue(dtc.description() != null && dtc.description().length() > 0); 54 | } 55 | 56 | if (dtcTimeSeries.hasPrior()) { 57 | dtcTimeSeries.loadPrior().toBlocking().subscribe(new Subscriber>() { 58 | @Override public void onCompleted() { 59 | 60 | } 61 | 62 | @Override public void onError(Throwable e) { 63 | System.out.println("Error: " + e.getMessage()); 64 | e.printStackTrace(); 65 | assertTrue(false); 66 | } 67 | 68 | @Override public void onNext(TimeSeries dtcTimeSeries) { 69 | // TODO - uncomment this when platform bug is resolved or explained 70 | //assertTrue(dtcTimeSeries.getItems().size() > 0); 71 | 72 | for(Dtc dtc : dtcTimeSeries.getItems()){ 73 | assertTrue(dtc.start() != null && dtc.start().length() > 0); 74 | assertTrue(dtc.vehicleId() != null && dtc.vehicleId().length() > 0); 75 | assertTrue(dtc.deviceId() != null && dtc.deviceId().length() > 0); 76 | assertTrue(dtc.number() != null && dtc.number().length() > 0); 77 | assertTrue(dtc.description() != null && dtc.description().length() > 0); 78 | } 79 | } 80 | }); 81 | } 82 | } 83 | }); 84 | } 85 | 86 | @Test 87 | public void testGetCodesByVehicleId(){ 88 | assertTrue(TestHelper.getVehicleId() != null); 89 | 90 | vinliApp.diagnostics().codes(TestHelper.getVehicleId(), null, null, null, null, null) 91 | .toBlocking().subscribe(new Subscriber>() { 92 | @Override 93 | public void onCompleted() { 94 | } 95 | 96 | @Override 97 | public void onError(Throwable e) { 98 | System.out.println("Error: " + e.getMessage()); 99 | e.printStackTrace(); 100 | assertTrue(false); 101 | } 102 | 103 | @Override 104 | public void onNext(TimeSeries dtcPage) { 105 | assertTrue(dtcPage.getItems().size() > 0); 106 | 107 | for(Dtc dtc : dtcPage.getItems()){ 108 | assertTrue(dtc.start() != null && dtc.start().length() > 0); 109 | assertTrue(dtc.vehicleId() != null && dtc.vehicleId().length() > 0); 110 | assertTrue(dtc.deviceId() != null && dtc.deviceId().length() > 0); 111 | assertTrue(dtc.number() != null && dtc.number().length() > 0); 112 | assertTrue(dtc.description() != null && dtc.description().length() > 0); 113 | } 114 | } 115 | }); 116 | } 117 | 118 | @Test 119 | public void testDiagnoseCode(){ 120 | final AtomicBoolean codeFound = new AtomicBoolean(false); 121 | vinliApp.diagnostics().diagnose("P0301") 122 | .flatMap(Page.allItems()) 123 | .toBlocking().subscribe(new Subscriber() { 124 | @Override 125 | public void onCompleted() { 126 | 127 | } 128 | 129 | @Override 130 | public void onError(Throwable e) { 131 | System.out.println("Error: " + e.getMessage()); 132 | e.printStackTrace(); 133 | assertTrue(false); 134 | } 135 | 136 | @Override 137 | public void onNext(Dtc.Code code) { 138 | codeFound.set(true); 139 | assertTrue(code.make() != null && code.make().length() > 0); 140 | } 141 | }); 142 | assertTrue(codeFound.get()); 143 | } 144 | 145 | @Test public void getCurrentBatteryStatus() { 146 | assertTrue(TestHelper.getVehicleId() != null); 147 | 148 | BatteryStatus.currentBatteryStatusForVehicle(TestHelper.getVehicleId()) 149 | .toBlocking() 150 | .subscribe(new Subscriber() { 151 | @Override public void onCompleted() { 152 | 153 | } 154 | 155 | @Override public void onError(Throwable e) { 156 | e.printStackTrace(); 157 | assertTrue(false); 158 | } 159 | 160 | @Override public void onNext(BatteryStatus batteryStatus) { 161 | if (batteryStatus != null) { 162 | assertTrue(batteryStatus.status() != null); 163 | assertTrue(batteryStatus.timestamp().length() > 0); 164 | } 165 | } 166 | }); 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Dummy.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import auto.parcel.AutoParcel; 7 | import com.google.gson.Gson; 8 | import com.google.gson.GsonBuilder; 9 | import com.google.gson.TypeAdapter; 10 | import com.google.gson.reflect.TypeToken; 11 | import com.google.gson.stream.JsonReader; 12 | import com.google.gson.stream.JsonWriter; 13 | import java.io.IOException; 14 | import java.lang.reflect.Type; 15 | import rx.Observable; 16 | 17 | /** 18 | * Created by JoshBeridon on 11/18/16. 19 | */ 20 | @AutoParcel public abstract class Dummy implements VinliItem { 21 | /*package*/ static final Type PAGE_TYPE = new TypeToken>() { 22 | }.getType(); 23 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { 24 | }.getType(); 25 | /*package*/ static final Type WRAPPED_TYPE_RUN = new TypeToken>() { 26 | }.getType(); 27 | 28 | static final void registerGson(GsonBuilder gb) { 29 | gb.registerTypeAdapter(Dummy.class, AutoParcelAdapter.create(AutoParcel_Dummy.class)); 30 | gb.registerTypeAdapter(Links.class, AutoParcelAdapter.create(AutoParcel_Dummy_Links.class)); 31 | gb.registerTypeAdapter(Run.class, AutoParcelAdapter.create(AutoParcel_Dummy_Run.class)); 32 | gb.registerTypeAdapter(Run.Status.class, 33 | AutoParcelAdapter.create(AutoParcel_Dummy_Run_Status.class)); 34 | gb.registerTypeAdapter(Run.Links.class, 35 | AutoParcelAdapter.create(AutoParcel_Dummy_Run_Links.class)); 36 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(Dummy.class, "dummies")); 37 | gb.registerTypeAdapter(PAGE_TYPE, Page.Adapter.create(PAGE_TYPE, Dummy.class, "dummies")); 38 | gb.registerTypeAdapter(WRAPPED_TYPE_RUN, Wrapped.Adapter.create(Run.class, "run")); 39 | 40 | gb.registerTypeAdapter(Run.Seed.class, new Dummy.Run.Seed.Adapter()); 41 | } 42 | 43 | public static Observable currentRun(@NonNull String dummyId) { 44 | return Vinli.curApp().run(dummyId); 45 | } 46 | 47 | @NonNull public abstract String id(); 48 | 49 | @NonNull public abstract String name(); 50 | 51 | @NonNull public abstract String caseId(); 52 | 53 | @NonNull public abstract String deviceId(); 54 | 55 | /*package*/ 56 | abstract Links links(); 57 | 58 | @AutoParcel 59 | /*package*/ static abstract class Links { 60 | public abstract String self(); 61 | 62 | public abstract String runs(); 63 | 64 | public abstract String device(); 65 | 66 | public abstract String messages(); 67 | 68 | public abstract String events(); 69 | 70 | /*package*/ Links() { 71 | } 72 | } 73 | 74 | @AutoParcel public static abstract class Run implements VinliItem { 75 | 76 | @NonNull public abstract String id(); 77 | 78 | /*package*/ 79 | @NonNull public abstract Status status(); 80 | 81 | /*package*/Run() { 82 | 83 | } 84 | 85 | @AutoParcel 86 | /*package*/ static abstract class Status implements Parcelable { 87 | 88 | public abstract String routeId(); 89 | 90 | public abstract boolean repeat(); 91 | 92 | public abstract String state(); 93 | 94 | // public abstract String lastLocation();//TODO this is not a string 95 | 96 | @Nullable public abstract String lastSpeed(); 97 | 98 | @Nullable public abstract Double lastRPM(); 99 | 100 | @Nullable public abstract Long lastMessageTime(); 101 | 102 | @Nullable public abstract Integer totalMessages(); 103 | 104 | @Nullable public abstract Integer sentMessages(); 105 | 106 | @Nullable public abstract Integer remaningMessages(); 107 | 108 | @Nullable public abstract Double remaningSeconds(); 109 | 110 | /*package*/ Status() { 111 | } 112 | } 113 | 114 | /*package*/ 115 | abstract Links links(); 116 | 117 | @AutoParcel 118 | /*package*/ static abstract class Links { 119 | public abstract String self(); 120 | 121 | /*package*/ Links() { 122 | } 123 | } 124 | 125 | @AutoParcel public static abstract class Seed { 126 | @NonNull public abstract String vin(); 127 | 128 | @NonNull public abstract String routeId(); 129 | 130 | @Nullable public abstract String repeat(); 131 | 132 | /*package*/ Seed() { 133 | } 134 | 135 | @AutoParcel.Builder public static abstract class Saver { 136 | public abstract Saver vin(@NonNull String s); 137 | 138 | public abstract Saver routeId(@NonNull String s); 139 | 140 | public abstract Saver repeat(@Nullable String s); 141 | 142 | /*package*/ Saver() { 143 | } 144 | 145 | /*package*/ 146 | abstract Seed autoBuild(); 147 | 148 | public Observable save(String dummyId) { 149 | final Seed s = autoBuild(); 150 | return Vinli.curApp() 151 | .dummies() 152 | .create(dummyId, s) 153 | .map(Wrapped.pluckItem()); 154 | } 155 | } 156 | 157 | /*package*/ static final class Adapter extends TypeAdapter { 158 | private Gson gson; 159 | 160 | @Override public void write(JsonWriter out, Run.Seed value) throws IOException { 161 | if (gson == null) { 162 | gson = Vinli.curApp().gson(); 163 | } 164 | 165 | out.beginObject(); 166 | out.name("run").beginObject(); 167 | out.name("vin").value(value.vin()); 168 | out.name("routeId").value(value.routeId()); 169 | out.name("repeat").value(value.repeat()); 170 | out.endObject(); 171 | out.endObject(); 172 | } 173 | 174 | @Override public Run.Seed read(JsonReader in) throws IOException { 175 | throw new UnsupportedOperationException("reading a RunSeed is not supported"); 176 | } 177 | } 178 | } 179 | 180 | public static final Run.Seed.Saver create() { 181 | return new AutoParcel_Dummy_Run_Seed.Builder(); 182 | } 183 | 184 | public Observable delete(String dummyId) { 185 | return Vinli.curApp().dummies().deleteRun(dummyId); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/SupportedPids.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.Locale; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | final class SupportedPids { 12 | 13 | private static final Set KNOWN_UNSUPPORTED_PIDS; 14 | 15 | static { 16 | KNOWN_UNSUPPORTED_PIDS = new HashSet<>(); 17 | 18 | // markers for supported pids 19 | KNOWN_UNSUPPORTED_PIDS.add("01-00"); 20 | KNOWN_UNSUPPORTED_PIDS.add("01-20"); 21 | KNOWN_UNSUPPORTED_PIDS.add("01-40"); 22 | KNOWN_UNSUPPORTED_PIDS.add("01-60"); 23 | KNOWN_UNSUPPORTED_PIDS.add("01-80"); 24 | 25 | // dtcs 26 | KNOWN_UNSUPPORTED_PIDS.add("01-01"); 27 | 28 | // accelerometer 29 | KNOWN_UNSUPPORTED_PIDS.add("01-0c"); 30 | 31 | // >2 byte pids from https://en.wikipedia.org/wiki/OBD-II_PIDs#Standard_PIDs 32 | KNOWN_UNSUPPORTED_PIDS.add("01-24"); 33 | KNOWN_UNSUPPORTED_PIDS.add("01-25"); 34 | KNOWN_UNSUPPORTED_PIDS.add("01-26"); 35 | KNOWN_UNSUPPORTED_PIDS.add("01-27"); 36 | KNOWN_UNSUPPORTED_PIDS.add("01-28"); 37 | KNOWN_UNSUPPORTED_PIDS.add("01-29"); 38 | KNOWN_UNSUPPORTED_PIDS.add("01-2a"); 39 | KNOWN_UNSUPPORTED_PIDS.add("01-2b"); 40 | 41 | KNOWN_UNSUPPORTED_PIDS.add("01-34"); 42 | KNOWN_UNSUPPORTED_PIDS.add("01-35"); 43 | KNOWN_UNSUPPORTED_PIDS.add("01-36"); 44 | KNOWN_UNSUPPORTED_PIDS.add("01-37"); 45 | KNOWN_UNSUPPORTED_PIDS.add("01-38"); 46 | KNOWN_UNSUPPORTED_PIDS.add("01-39"); 47 | KNOWN_UNSUPPORTED_PIDS.add("01-3a"); 48 | KNOWN_UNSUPPORTED_PIDS.add("01-3b"); 49 | 50 | KNOWN_UNSUPPORTED_PIDS.add("01-41"); 51 | KNOWN_UNSUPPORTED_PIDS.add("01-4f"); 52 | KNOWN_UNSUPPORTED_PIDS.add("01-50"); 53 | 54 | KNOWN_UNSUPPORTED_PIDS.add("01-64"); 55 | //KNOWN_UNSUPPORTED_PIDS.add("01-66"); 56 | KNOWN_UNSUPPORTED_PIDS.add("01-67"); 57 | KNOWN_UNSUPPORTED_PIDS.add("01-68"); 58 | KNOWN_UNSUPPORTED_PIDS.add("01-69"); 59 | KNOWN_UNSUPPORTED_PIDS.add("01-6a"); 60 | KNOWN_UNSUPPORTED_PIDS.add("01-6b"); 61 | KNOWN_UNSUPPORTED_PIDS.add("01-6c"); 62 | KNOWN_UNSUPPORTED_PIDS.add("01-6d"); 63 | KNOWN_UNSUPPORTED_PIDS.add("01-6e"); 64 | KNOWN_UNSUPPORTED_PIDS.add("01-6f"); 65 | 66 | KNOWN_UNSUPPORTED_PIDS.add("01-70"); 67 | KNOWN_UNSUPPORTED_PIDS.add("01-71"); 68 | KNOWN_UNSUPPORTED_PIDS.add("01-72"); 69 | KNOWN_UNSUPPORTED_PIDS.add("01-73"); 70 | KNOWN_UNSUPPORTED_PIDS.add("01-74"); 71 | KNOWN_UNSUPPORTED_PIDS.add("01-75"); 72 | KNOWN_UNSUPPORTED_PIDS.add("01-76"); 73 | KNOWN_UNSUPPORTED_PIDS.add("01-77"); 74 | KNOWN_UNSUPPORTED_PIDS.add("01-78"); 75 | KNOWN_UNSUPPORTED_PIDS.add("01-79"); 76 | KNOWN_UNSUPPORTED_PIDS.add("01-7a"); 77 | KNOWN_UNSUPPORTED_PIDS.add("01-7b"); 78 | KNOWN_UNSUPPORTED_PIDS.add("01-7c"); 79 | KNOWN_UNSUPPORTED_PIDS.add("01-7f"); 80 | 81 | KNOWN_UNSUPPORTED_PIDS.add("01-81"); 82 | KNOWN_UNSUPPORTED_PIDS.add("01-82"); 83 | KNOWN_UNSUPPORTED_PIDS.add("01-83"); 84 | 85 | KNOWN_UNSUPPORTED_PIDS.add("01-a0"); 86 | KNOWN_UNSUPPORTED_PIDS.add("01-c0"); 87 | } 88 | 89 | private final String raw; 90 | private HashMap supportMap; 91 | 92 | /*package*/ SupportedPids(@NonNull String raw) { 93 | this.raw = raw; 94 | } 95 | 96 | public String getRaw() { 97 | return raw; 98 | } 99 | 100 | public boolean supports(@NonNull String code) { 101 | if (!(code = code.toLowerCase(Locale.US)).startsWith("01-")) return false; 102 | if (KNOWN_UNSUPPORTED_PIDS.contains(code)) return false; 103 | if (supportMap == null) buildSupportMap(); 104 | Boolean result = supportMap.get(code); 105 | return result == null 106 | ? false 107 | : result; 108 | } 109 | 110 | @NonNull 111 | public String[] getSupport() { 112 | if (supportMap == null) buildSupportMap(); 113 | Set support = new HashSet<>(); 114 | for (Map.Entry e : supportMap.entrySet()) { 115 | if (e.getValue() != null && e.getValue()) { 116 | support.add(e.getKey().toUpperCase(Locale.US)); 117 | } 118 | } 119 | return support.toArray(new String[support.size()]); 120 | } 121 | 122 | @Override 123 | public String toString() { 124 | if (supportMap == null) buildSupportMap(); 125 | StringBuilder sb = new StringBuilder(); 126 | for (Map.Entry entry : supportMap.entrySet()) { 127 | String key = entry.getKey(); 128 | Boolean val = entry.getValue(); 129 | if (sb.length() != 0) sb.append("::"); 130 | sb.append("key='").append(key).append("',val='").append(val).append("'"); 131 | } 132 | return sb.toString(); 133 | } 134 | 135 | private void buildSupportMap() { 136 | supportMap = new HashMap<>(); 137 | ArrayList groups = new ArrayList<>(); 138 | StringBuilder sb = new StringBuilder(); 139 | for (int i = 0; i < raw.length(); i++) { 140 | if (sb.length() == 8) { 141 | groups.add(sb.toString()); 142 | sb.delete(0, 8); 143 | } 144 | sb.append(raw.charAt(i)); 145 | } 146 | if (sb.length() == 8) { 147 | groups.add(sb.toString()); 148 | } 149 | for (int i = 0; i < groups.size(); i++) { 150 | parseBitflags(i, groups.get(i)); 151 | } 152 | } 153 | 154 | private void parseBitflags(int group, String bitflags) { 155 | int groupStart = group * 32 + 1; 156 | for (int i = 0; i < 8; i++) { 157 | int j = i * 4; 158 | int hexInt = Integer.parseInt(bitflags.substring(i, i + 1), 16); 159 | String bin = Integer.toBinaryString(hexInt); 160 | while (bin.length() < 4) bin = '0' + bin; 161 | putIntoMap(groupStart + j, bin.charAt(0) == '1'); 162 | putIntoMap(groupStart + j + 1, bin.charAt(1) == '1'); 163 | putIntoMap(groupStart + j + 2, bin.charAt(2) == '1'); 164 | putIntoMap(groupStart + j + 3, bin.charAt(3) == '1'); 165 | } 166 | } 167 | 168 | private void putIntoMap(int index, boolean flag) { 169 | String hex = Integer.toHexString(index).toLowerCase(Locale.US); 170 | while (hex.length() < 2) hex = '0' + hex; 171 | supportMap.put("01-" + hex, flag); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /android-net/src/test/java/li/vin/net/CollisionsIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.robolectric.RobolectricTestRunner; 7 | import org.robolectric.annotation.Config; 8 | 9 | import rx.Subscriber; 10 | 11 | import static junit.framework.Assert.assertTrue; 12 | 13 | @RunWith(RobolectricTestRunner.class) 14 | @Config(constants = BuildConfig.class, sdk = 22) 15 | public class CollisionsIntegrationTests { 16 | 17 | public VinliApp vinliApp; 18 | 19 | @Before 20 | public void setup(){ 21 | assertTrue(TestHelper.getAccessToken() != null); 22 | 23 | vinliApp = TestHelper.getVinliApp(); 24 | } 25 | 26 | @Test 27 | public void testGetPagedCollisions(){ 28 | Collision.collisionsWithDeviceId(TestHelper.getDeviceId(), (Long) null, null, 1, null) 29 | .toBlocking().subscribe(new Subscriber>() { 30 | @Override 31 | public void onCompleted() { 32 | 33 | } 34 | 35 | @Override 36 | public void onError(Throwable e) { 37 | e.printStackTrace(); 38 | assertTrue(false); 39 | } 40 | 41 | @Override 42 | public void onNext(TimeSeries collisionTimeSeries) { 43 | assertTrue(collisionTimeSeries.getItems().size() > 0); 44 | 45 | for(Collision collision : collisionTimeSeries.getItems()){ 46 | assertTrue(collision.id() != null && collision.id().length() > 0); 47 | assertTrue(collision.deviceId().length() > 0); 48 | assertTrue(collision.vehicleId().length() > 0); 49 | assertTrue(collision.timestamp().length() > 0); 50 | } 51 | 52 | if (collisionTimeSeries.hasPrior()) { 53 | collisionTimeSeries.loadPrior() 54 | .toBlocking().subscribe(new Subscriber>() { 55 | @Override public void onCompleted() { 56 | 57 | } 58 | 59 | @Override public void onError(Throwable e) { 60 | e.printStackTrace(); 61 | assertTrue(false); 62 | } 63 | 64 | @Override public void onNext(TimeSeries collisionTimeSeries) { 65 | assertTrue(collisionTimeSeries.getItems().size() > 0); 66 | 67 | for(Collision collision : collisionTimeSeries.getItems()){ 68 | assertTrue(collision.id() != null && collision.id().length() > 0); 69 | assertTrue(collision.deviceId().length() > 0); 70 | assertTrue(collision.vehicleId().length() > 0); 71 | assertTrue(collision.timestamp().length() > 0); 72 | } 73 | } 74 | }); 75 | } 76 | } 77 | }); 78 | } 79 | 80 | @Test 81 | public void testGetCollisionsByVehicleId(){ 82 | Collision.collisionsWithVehicleId(TestHelper.getVehicleId(), (Long) null, null, null, null).toBlocking().subscribe(new Subscriber>() { 83 | @Override 84 | public void onCompleted() { 85 | 86 | } 87 | 88 | @Override 89 | public void onError(Throwable e) { 90 | e.printStackTrace(); 91 | assertTrue(false); 92 | } 93 | 94 | @Override 95 | public void onNext(TimeSeries collisionTimeSeries) { 96 | assertTrue(collisionTimeSeries.getItems().size() > 0); 97 | 98 | for(Collision collision : collisionTimeSeries.getItems()){ 99 | assertTrue(collision.id() != null && collision.id().length() > 0); 100 | assertTrue(collision.deviceId().length() > 0); 101 | assertTrue(collision.vehicleId().length() > 0); 102 | assertTrue(collision.timestamp().length() > 0); 103 | } 104 | } 105 | }); 106 | } 107 | 108 | @Test 109 | public void testGetCollisionsByDeviceId(){ 110 | Collision.collisionsWithDeviceId(TestHelper.getDeviceId(), (Long) null, null, null, null).toBlocking().subscribe(new Subscriber>() { 111 | @Override 112 | public void onCompleted() { 113 | 114 | } 115 | 116 | @Override 117 | public void onError(Throwable e) { 118 | e.printStackTrace(); 119 | assertTrue(false); 120 | } 121 | 122 | @Override 123 | public void onNext(TimeSeries collisionTimeSeries) { 124 | assertTrue(collisionTimeSeries.getItems().size() > 0); 125 | 126 | for(Collision collision : collisionTimeSeries.getItems()){ 127 | assertTrue(collision.id() != null && collision.id().length() > 0); 128 | assertTrue(collision.deviceId().length() > 0); 129 | assertTrue(collision.vehicleId().length() > 0); 130 | assertTrue(collision.timestamp().length() > 0); 131 | } 132 | } 133 | }); 134 | } 135 | 136 | @Test 137 | public void getCollisionById(){ 138 | Collision.collisionWithId(TestHelper.getCollisionId()).toBlocking().subscribe( 139 | new Subscriber() { 140 | @Override public void onCompleted() { 141 | 142 | } 143 | 144 | @Override public void onError(Throwable e) { 145 | assertTrue(false); 146 | } 147 | 148 | @Override public void onNext(Collision collision) { 149 | assertTrue(collision.id() != null && collision.id().length() > 0); 150 | assertTrue(collision.deviceId().length() > 0); 151 | assertTrue(collision.vehicleId().length() > 0); 152 | assertTrue(collision.timestamp().length() > 0); 153 | } 154 | }); 155 | } 156 | 157 | @Test public void getCollisionsByUrl() { 158 | assertTrue(TestHelper.getDeviceId() != null); 159 | 160 | vinliApp.collisions() 161 | .collisionsForUrl(String.format("%sdevices/%s/collisions", VinliEndpoint.SAFETY.getUrl(), 162 | TestHelper.getDeviceId())) 163 | .toBlocking() 164 | .subscribe(new Subscriber>() { 165 | @Override public void onCompleted() { 166 | 167 | } 168 | 169 | @Override public void onError(Throwable e) { 170 | e.printStackTrace(); 171 | assertTrue(false); 172 | } 173 | 174 | @Override public void onNext(TimeSeries collisionTimeSeries) { 175 | assertTrue(collisionTimeSeries.getItems().size() > 0); 176 | 177 | for(Collision collision : collisionTimeSeries.getItems()){ 178 | assertTrue(collision.id() != null && collision.id().length() > 0); 179 | assertTrue(collision.deviceId().length() > 0); 180 | assertTrue(collision.vehicleId().length() > 0); 181 | assertTrue(collision.timestamp().length() > 0); 182 | } 183 | } 184 | }); 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /android-net/src/test/java/li/vin/net/VehiclesIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.robolectric.RobolectricTestRunner; 7 | import org.robolectric.annotation.Config; 8 | 9 | import rx.Subscriber; 10 | 11 | import static junit.framework.Assert.assertTrue; 12 | 13 | @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 22) 14 | public class VehiclesIntegrationTests { 15 | 16 | public VinliApp vinliApp; 17 | 18 | @Before public void setup() { 19 | assertTrue(TestHelper.getAccessToken() != null); 20 | 21 | vinliApp = TestHelper.getVinliApp(); 22 | } 23 | 24 | @Test public void testGetVehicleByDeviceId() { 25 | assertTrue(TestHelper.getDeviceId() != null); 26 | 27 | Vehicle.vehiclesWithDeviceId(TestHelper.getDeviceId(), 1, null) 28 | .toBlocking() 29 | .subscribe(new Subscriber>() { 30 | @Override public void onCompleted() { 31 | } 32 | 33 | @Override public void onError(Throwable e) { 34 | System.out.println("Error: " + e.getMessage()); 35 | e.printStackTrace(); 36 | assertTrue(false); 37 | } 38 | 39 | @Override public void onNext(Page vehiclePage) { 40 | assertTrue(vehiclePage.getItems().size() > 0); 41 | 42 | for (Vehicle vehicle : vehiclePage.getItems()) { 43 | assertTrue(vehicle.id() != null && vehicle.id().length() > 0); 44 | assertTrue(vehicle.vin() != null && vehicle.vin().length() > 0); 45 | } 46 | 47 | if (vehiclePage.hasNextPage()) { 48 | vehiclePage.loadNextPage() 49 | .toBlocking().subscribe(new Subscriber>() { 50 | @Override public void onCompleted() { 51 | 52 | } 53 | 54 | @Override public void onError(Throwable e) { 55 | System.out.println("Error: " + e.getMessage()); 56 | e.printStackTrace(); 57 | assertTrue(false); 58 | } 59 | 60 | @Override public void onNext(Page vehiclePage) { 61 | assertTrue(vehiclePage.getItems().size() > 0); 62 | 63 | for (Vehicle vehicle : vehiclePage.getItems()) { 64 | assertTrue(vehicle.id() != null && vehicle.id().length() > 0); 65 | assertTrue(vehicle.vin() != null && vehicle.vin().length() > 0); 66 | } 67 | } 68 | }); 69 | } 70 | } 71 | }); 72 | } 73 | 74 | @Test public void testGetLatestVehicleByDeviceId() { 75 | assertTrue(TestHelper.getDeviceId() != null); 76 | 77 | Vehicle.latestVehicleWithDeviceId(TestHelper.getDeviceId()) 78 | .toBlocking() 79 | .subscribe(new Subscriber() { 80 | @Override public void onCompleted() { 81 | 82 | } 83 | 84 | @Override public void onError(Throwable e) { 85 | System.out.println("Error: " + e.getMessage()); 86 | e.printStackTrace(); 87 | assertTrue(false); 88 | } 89 | 90 | @Override public void onNext(Vehicle vehicle) { 91 | assertTrue(vehicle.id() != null && vehicle.id().length() > 0); 92 | assertTrue(vehicle.vin() != null && vehicle.vin().length() > 0); 93 | } 94 | }); 95 | } 96 | 97 | @Test public void testGetVehicleWithLimitOffsetByDeviceId() { 98 | assertTrue(TestHelper.getDeviceId() != null); 99 | 100 | vinliApp.vehicles() 101 | .vehicles(TestHelper.getDeviceId(), 5, 1) 102 | .toBlocking() 103 | .subscribe(new Subscriber>() { 104 | @Override public void onCompleted() { 105 | } 106 | 107 | @Override public void onError(Throwable e) { 108 | System.out.println("Error: " + e.getMessage()); 109 | e.printStackTrace(); 110 | assertTrue(false); 111 | } 112 | 113 | @Override public void onNext(Page vehiclePage) { 114 | assertTrue(vehiclePage.getItems().size() <= 5); 115 | 116 | for (Vehicle vehicle : vehiclePage.getItems()) { 117 | assertTrue(vehicle.id() != null && vehicle.id().length() > 0); 118 | assertTrue(vehicle.vin() != null && vehicle.vin().length() > 0); 119 | } 120 | } 121 | }); 122 | } 123 | 124 | @Test public void testGetVehicleById() { 125 | assertTrue(TestHelper.getVehicleId() != null); 126 | 127 | Vehicle.vehicleWithId(TestHelper.getVehicleId()) 128 | .toBlocking() 129 | .subscribe(new Subscriber() { 130 | @Override public void onCompleted() { 131 | 132 | } 133 | 134 | @Override public void onError(Throwable e) { 135 | System.out.println("Error: " + e.getMessage()); 136 | e.printStackTrace(); 137 | assertTrue(false); 138 | } 139 | 140 | @Override public void onNext(Vehicle vehicle) { 141 | assertTrue(vehicle.engine()!=null); 142 | assertTrue(vehicle.engineDisplacement()!=null); 143 | assertTrue(vehicle.transmission()!=null); 144 | assertTrue(vehicle.manufacturer()!=null); 145 | assertTrue(vehicle.categories()!=null); 146 | assertTrue(vehicle.epaMpg()!=null); 147 | assertTrue(vehicle.drive()!=null); 148 | assertTrue(vehicle.numDoors()!=null); 149 | assertTrue(vehicle.id() != null && vehicle.id().length() > 0); 150 | assertTrue(vehicle.vin() != null && vehicle.vin().length() > 0); 151 | } 152 | }); 153 | } 154 | 155 | @Test public void getVehiclesByUrl() { 156 | assertTrue(TestHelper.getDeviceId() != null); 157 | 158 | vinliApp.vehicles() 159 | .vehiclesForUrl(String.format("%sdevices/%s/vehicles", VinliEndpoint.PLATFORM.getUrl(), 160 | TestHelper.getDeviceId())) 161 | .toBlocking() 162 | .subscribe(new Subscriber>() { 163 | @Override public void onCompleted() { 164 | 165 | } 166 | 167 | @Override public void onError(Throwable e) { 168 | e.printStackTrace(); 169 | assertTrue(false); 170 | } 171 | 172 | @Override public void onNext(Page vehiclePage) { 173 | assertTrue(vehiclePage.getItems().size() > 0); 174 | 175 | for (Vehicle vehicle : vehiclePage.getItems()) { 176 | assertTrue(vehicle.id() != null && vehicle.id().length() > 0); 177 | assertTrue(vehicle.vin() != null && vehicle.vin().length() > 0); 178 | } 179 | } 180 | }); 181 | } 182 | 183 | 184 | } -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/Location.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import auto.parcel.AutoParcel; 6 | import com.google.gson.Gson; 7 | import com.google.gson.GsonBuilder; 8 | import com.google.gson.TypeAdapter; 9 | import com.google.gson.reflect.TypeToken; 10 | import com.google.gson.stream.JsonReader; 11 | import com.google.gson.stream.JsonToken; 12 | import com.google.gson.stream.JsonWriter; 13 | import java.io.IOException; 14 | import java.lang.reflect.Type; 15 | import java.util.ArrayList; 16 | import java.util.Date; 17 | import java.util.List; 18 | import rx.Observable; 19 | 20 | @AutoParcel 21 | public abstract class Location implements VinliItem { 22 | /*package*/ static final Type TIME_SERIES_TYPE = new TypeToken>() { }.getType(); 23 | 24 | /*package*/ static final void registerGson(GsonBuilder gb) { 25 | gb.registerTypeAdapter(Location.class, new LocationAdapter()); 26 | gb.registerTypeAdapter(TIME_SERIES_TYPE, new LocationTimeSeriesAdapter()); 27 | } 28 | 29 | public static Observable> locationsWithDeviceId(@NonNull String deviceId) { 30 | return locationsWithDeviceId(deviceId, (Long) null, null, null, null); 31 | } 32 | 33 | public static Observable> locationsWithDeviceId(@NonNull String deviceId, 34 | @Nullable Long sinceMs, @Nullable Long untilMs, @Nullable Integer limit, 35 | @Nullable String sortDir) { 36 | return Vinli.curApp().locations().locations(deviceId, sinceMs, untilMs, limit, sortDir); 37 | } 38 | 39 | public static Observable> locationsWithVehicleId(@NonNull String vehicleId) { 40 | return locationsWithVehicleId(vehicleId, (Long) null, null, null, null); 41 | } 42 | 43 | public static Observable> locationsWithVehicleId(@NonNull String vehicleId, 44 | @Nullable Long sinceMs, @Nullable Long untilMs, @Nullable Integer limit, 45 | @Nullable String sortDir) { 46 | return Vinli.curApp().locations().vehicleLocations(vehicleId, sinceMs, untilMs, limit, sortDir); 47 | } 48 | 49 | @Deprecated 50 | public static Observable> locationsWithDeviceId(@NonNull String deviceId, 51 | @Nullable Date since, @Nullable Date until, @Nullable Integer limit, 52 | @Nullable String sortDir) { 53 | Long sinceMs = since == null ? null : since.getTime(); 54 | Long untilMs = until == null ? null : until.getTime(); 55 | return Vinli.curApp().locations().locations(deviceId, sinceMs, untilMs, limit, sortDir); 56 | } 57 | 58 | public abstract Coordinate coordinate(); 59 | public abstract String timestamp(); 60 | 61 | /*package*/ Location() { } 62 | 63 | @AutoParcel.Builder 64 | /*package*/ interface Builder { 65 | Builder id(String s); 66 | Builder coordinate(Coordinate c); 67 | Builder timestamp(String s); 68 | 69 | Location build(); 70 | } 71 | 72 | private static final class LocationAdapter extends TypeAdapter { 73 | private Gson gson; 74 | 75 | @Override public void write(JsonWriter out, Location value) throws IOException { 76 | throw new UnsupportedOperationException("writing a location is not supported"); 77 | } 78 | 79 | @Override public Location read(JsonReader in) throws IOException { 80 | if (gson == null) { 81 | gson = Vinli.curApp().gson(); 82 | } 83 | 84 | if(in.peek() == JsonToken.NULL){ 85 | in.nextNull(); 86 | return null; 87 | } 88 | 89 | final Location.Builder b = new AutoParcel_Location.Builder(); 90 | 91 | in.beginObject(); 92 | while (in.hasNext()) { 93 | final String locationName = in.nextName(); 94 | 95 | switch (locationName) { 96 | case "type": in.skipValue(); break; 97 | case "geometry": 98 | in.beginObject(); 99 | while (in.hasNext()) { 100 | final String geoName = in.nextName(); 101 | 102 | switch (geoName) { 103 | case "type": in.skipValue(); break; 104 | case "coordinates": b.coordinate(gson.fromJson(in, Coordinate.class)); break; 105 | default: in.skipValue(); break; 106 | } 107 | } 108 | in.endObject(); 109 | break; 110 | case "properties": 111 | in.beginObject(); 112 | while (in.hasNext()) { 113 | final String propName = in.nextName(); 114 | 115 | switch (propName) { 116 | case "id": b.id(in.nextString()); break; 117 | case "timestamp": b.timestamp(in.nextString()); break; 118 | case "links": in.skipValue(); break; 119 | case "data": 120 | in.beginObject(); 121 | in.endObject(); 122 | break; 123 | default: in.skipValue(); break; 124 | } 125 | } 126 | in.endObject(); 127 | break; 128 | default: in.skipValue(); break; 129 | } 130 | } 131 | in.endObject(); 132 | 133 | return b.build(); 134 | } 135 | } 136 | 137 | /*package*/ static final class LocationTimeSeriesAdapter extends TypeAdapter> { 138 | 139 | private Gson gson; 140 | 141 | @Override public void write(JsonWriter out, TimeSeries value) throws IOException { 142 | throw new UnsupportedOperationException("writing a location time series is not supported"); 143 | } 144 | 145 | @Override public TimeSeries read(JsonReader in) throws IOException { 146 | if (gson == null) { 147 | gson = Vinli.curApp().gson(); 148 | } 149 | 150 | final TimeSeries.Builder b = new AutoParcel_TimeSeries.Builder() 151 | .type(TIME_SERIES_TYPE) 152 | .className(this.getClass().getName()); 153 | 154 | in.beginObject(); 155 | while (in.hasNext()) { 156 | final String name = in.nextName(); 157 | 158 | switch (name) { 159 | case "meta": b.meta(gson.fromJson(in, TimeSeries.Meta.class)); break; 160 | case "locations": 161 | in.beginObject(); 162 | while (in.hasNext()) { 163 | final String locName = in.nextName(); 164 | 165 | switch (locName) { 166 | case "type": in.skipValue(); break; 167 | case "features": 168 | final List locations = new ArrayList<>(); 169 | 170 | in.beginArray(); 171 | while (in.hasNext()) { 172 | locations.add(gson.fromJson(in, Location.class)); 173 | } 174 | in.endArray(); 175 | 176 | b.items(locations); 177 | break; 178 | default: in.skipValue(); break; 179 | } 180 | } 181 | in.endObject(); 182 | break; 183 | default: in.skipValue(); break; 184 | } 185 | } 186 | in.endObject(); 187 | 188 | return b.build(); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /android-net/src/test/java/li/vin/net/NotificationsIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.robolectric.RobolectricTestRunner; 7 | import org.robolectric.annotation.Config; 8 | 9 | import rx.Subscriber; 10 | 11 | import static junit.framework.Assert.assertTrue; 12 | 13 | @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 22) 14 | public class NotificationsIntegrationTests { 15 | 16 | public VinliApp vinliApp; 17 | 18 | @Before public void setup() { 19 | assertTrue(TestHelper.getAccessToken() != null); 20 | 21 | vinliApp = TestHelper.getVinliApp(); 22 | } 23 | 24 | @Test public void testGetNotificationsFromEvent() { 25 | assertTrue(TestHelper.getEventId() != null); 26 | 27 | Notification.notificationsWithEventId(TestHelper.getEventId(), (Long) null, null, null, null) 28 | .toBlocking() 29 | .subscribe(new Subscriber>() { 30 | @Override public void onCompleted() { 31 | 32 | } 33 | 34 | @Override public void onError(Throwable e) { 35 | e.printStackTrace(); 36 | assertTrue(false); 37 | } 38 | 39 | @Override public void onNext(TimeSeries notificationTimeSeries) { 40 | assertTrue(notificationTimeSeries.getItems().size() > 0); 41 | 42 | for (Notification notification : notificationTimeSeries.getItems()) { 43 | assertTrue(notification.id() != null && notification.id().length() > 0); 44 | assertTrue(notification.eventTimestamp() != null 45 | && notification.eventTimestamp().length() > 0); 46 | assertTrue(notification.eventType() != null && notification.eventType().length() > 0); 47 | assertTrue(notification.eventId() != null && notification.eventId().length() > 0); 48 | assertTrue(notification.subscriptionId() != null 49 | && notification.subscriptionId().length() > 0); 50 | } 51 | } 52 | }); 53 | } 54 | 55 | @Test public void testGetNotificationsForSubscription() { 56 | assertTrue(TestHelper.getSubscriptionId() != null); 57 | 58 | Notification 59 | .notificationsWithSubscriptionId(TestHelper.getSubscriptionId(), (Long) null, null, 1, null) 60 | .toBlocking() 61 | .subscribe(new Subscriber>() { 62 | @Override public void onCompleted() { 63 | 64 | } 65 | 66 | @Override public void onError(Throwable e) { 67 | e.printStackTrace(); 68 | assertTrue(false); 69 | } 70 | 71 | @Override public void onNext(TimeSeries notificationTimeSeries) { 72 | assertTrue(notificationTimeSeries.getItems().size() > 0); 73 | 74 | for (Notification notification : notificationTimeSeries.getItems()) { 75 | assertTrue(notification.id() != null && notification.id().length() > 0); 76 | assertTrue(notification.eventTimestamp() != null 77 | && notification.eventTimestamp().length() > 0); 78 | assertTrue(notification.eventType() != null && notification.eventType().length() > 0); 79 | assertTrue(notification.eventId() != null && notification.eventId().length() > 0); 80 | assertTrue(notification.subscriptionId() != null 81 | && notification.subscriptionId().length() > 0); 82 | } 83 | 84 | if (notificationTimeSeries.hasPrior()) { 85 | notificationTimeSeries.loadPrior() 86 | .toBlocking().subscribe(new Subscriber>() { 87 | @Override public void onCompleted() { 88 | 89 | } 90 | 91 | @Override public void onError(Throwable e) { 92 | e.printStackTrace(); 93 | assertTrue(false); 94 | } 95 | 96 | @Override public void onNext(TimeSeries notificationTimeSeries) { 97 | assertTrue(notificationTimeSeries.getItems().size() > 0); 98 | 99 | for (Notification notification : notificationTimeSeries.getItems()) { 100 | assertTrue(notification.id() != null && notification.id().length() > 0); 101 | assertTrue(notification.eventTimestamp() != null 102 | && notification.eventTimestamp().length() > 0); 103 | assertTrue(notification.eventType() != null && notification.eventType().length() > 0); 104 | assertTrue(notification.eventId() != null && notification.eventId().length() > 0); 105 | assertTrue(notification.subscriptionId() != null 106 | && notification.subscriptionId().length() > 0); 107 | } 108 | } 109 | }); 110 | } 111 | } 112 | }); 113 | } 114 | 115 | @Test public void testGetNotification() { 116 | assertTrue(TestHelper.getNotificationId() != null); 117 | 118 | Notification.notificationWithId(TestHelper.getNotificationId()) 119 | .toBlocking() 120 | .subscribe(new Subscriber() { 121 | @Override public void onCompleted() { 122 | 123 | } 124 | 125 | @Override public void onError(Throwable e) { 126 | e.printStackTrace(); 127 | assertTrue(false); 128 | } 129 | 130 | @Override public void onNext(Notification notification) { 131 | assertTrue(notification.id() != null && notification.id().length() > 0); 132 | assertTrue(notification.eventTimestamp() != null 133 | && notification.eventTimestamp().length() > 0); 134 | assertTrue(notification.eventType() != null && notification.eventType().length() > 0); 135 | assertTrue(notification.eventId() != null && notification.eventId().length() > 0); 136 | assertTrue(notification.subscriptionId() != null 137 | && notification.subscriptionId().length() > 0); 138 | } 139 | }); 140 | } 141 | 142 | @Test public void getNotificationsByUrl() { 143 | assertTrue(TestHelper.getEventId() != null); 144 | 145 | vinliApp.notifications() 146 | .notificationsForUrl(String.format("%sevents/%s/notifications", VinliEndpoint.EVENTS.getUrl(), 147 | TestHelper.getEventId())) 148 | .toBlocking() 149 | .subscribe(new Subscriber>() { 150 | @Override public void onCompleted() { 151 | 152 | } 153 | 154 | @Override public void onError(Throwable e) { 155 | e.printStackTrace(); 156 | assertTrue(false); 157 | } 158 | 159 | @Override public void onNext(TimeSeries notificationTimeSeries) { 160 | assertTrue(notificationTimeSeries.getItems().size() > 0); 161 | 162 | for (Notification notification : notificationTimeSeries.getItems()) { 163 | assertTrue(notification.id() != null && notification.id().length() > 0); 164 | assertTrue(notification.eventTimestamp() != null 165 | && notification.eventTimestamp().length() > 0); 166 | assertTrue(notification.eventType() != null && notification.eventType().length() > 0); 167 | assertTrue(notification.eventId() != null && notification.eventId().length() > 0); 168 | assertTrue(notification.subscriptionId() != null 169 | && notification.subscriptionId().length() > 0); 170 | } 171 | } 172 | }); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/OdometerTrigger.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import auto.parcel.AutoParcel; 7 | import com.google.gson.Gson; 8 | import com.google.gson.GsonBuilder; 9 | import com.google.gson.TypeAdapter; 10 | import com.google.gson.reflect.TypeToken; 11 | import com.google.gson.stream.JsonReader; 12 | import com.google.gson.stream.JsonWriter; 13 | import java.io.IOException; 14 | import java.lang.reflect.Type; 15 | import java.util.Date; 16 | import rx.Observable; 17 | 18 | @AutoParcel 19 | public abstract class OdometerTrigger implements VinliItem{ 20 | 21 | public enum TriggerType{ 22 | SPECIFIC("specific"), 23 | FROM_NOW("from_now"), 24 | MILESTONE("milestone"); 25 | 26 | private String typeStr; 27 | 28 | private TriggerType(String unit){ 29 | this.typeStr = unit; 30 | } 31 | 32 | /*package*/ String getTriggerTypeStr(){ 33 | return this.typeStr; 34 | } 35 | 36 | /*package*/ static TriggerType getEnumFromString(String str){ 37 | switch(str){ 38 | case "specific": 39 | return SPECIFIC; 40 | case "from_now": 41 | return FROM_NOW; 42 | case "milestone": 43 | return MILESTONE; 44 | default: 45 | throw new IllegalArgumentException("str is not a valid string to be used for TriggerType"); 46 | } 47 | } 48 | } 49 | 50 | /*package*/ static final Type TIME_SERIES_TYPE = new TypeToken>() { }.getType(); 51 | /*package*/ static final Type WRAPPED_TYPE = new TypeToken>() { }.getType(); 52 | 53 | /*package*/ static final void registerGson(GsonBuilder gb) { 54 | gb.registerTypeAdapter(OdometerTrigger.class, new OdometerTriggerAdapter()); 55 | gb.registerTypeAdapter(Links.class, AutoParcelAdapter.create(AutoParcel_OdometerTrigger_Links.class)); 56 | gb.registerTypeAdapter(OdometerTrigger.Seed.class, new Seed.Adapter()); 57 | 58 | gb.registerTypeAdapter(WRAPPED_TYPE, Wrapped.Adapter.create(OdometerTrigger.class, "odometerTrigger")); 59 | gb.registerTypeAdapter(TIME_SERIES_TYPE, TimeSeries.Adapter.create(TIME_SERIES_TYPE, OdometerTrigger.class, "odometerTriggers")); 60 | } 61 | 62 | public static Observable odometerTriggerWithId( 63 | @NonNull String odometerTriggerId) { 64 | return Vinli.curApp().odometerTrigger(odometerTriggerId); 65 | } 66 | 67 | public static Observable> odometerTriggersWithVehicleId( 68 | @NonNull String vehicleId) { 69 | return odometerTriggersWithVehicleId(vehicleId, (Long) null, null, null, null); 70 | } 71 | 72 | public static Observable> odometerTriggersWithVehicleId(@NonNull String vehicleId, @Nullable Long sinceMs, @Nullable Long untilMs, @Nullable Integer limit, @Nullable String sortDir){ 73 | return Vinli.curApp().distances().odometerTriggers(vehicleId, sinceMs, untilMs, limit, sortDir); 74 | } 75 | 76 | @Deprecated 77 | public static Observable> odometerTriggersWithVehicleId( 78 | @NonNull String vehicleId, @Nullable Date since, @Nullable Date until, 79 | @Nullable Integer limit, @Nullable String sortDir) { 80 | Long sinceMs = since == null ? null : since.getTime(); 81 | Long untilMs = until == null ? null : until.getTime(); 82 | return Vinli.curApp().distances().odometerTriggers(vehicleId, sinceMs, untilMs, limit, sortDir); 83 | } 84 | 85 | public abstract String vehicleId(); 86 | public abstract TriggerType type(); 87 | public abstract Double threshold(); 88 | public abstract Double events(); 89 | @Nullable public abstract DistanceUnit unit(); 90 | 91 | /*package*/ abstract Links links(); 92 | 93 | public static final Seed.Saver create() { 94 | return new AutoParcel_OdometerTrigger_Seed.Builder(); 95 | } 96 | 97 | public Observable delete(){ 98 | return Vinli.curApp().distances().deleteOdometerTrigger(id()); 99 | } 100 | 101 | @AutoParcel 102 | /*package*/ static abstract class Links implements Parcelable { 103 | public abstract String vehicle(); 104 | 105 | /*package*/ Links() { } 106 | } 107 | 108 | @AutoParcel.Builder 109 | /*package*/ static abstract class Builder{ 110 | public abstract Builder id(String id); 111 | public abstract Builder vehicleId(String vehicleId); 112 | public abstract Builder type(TriggerType type); 113 | public abstract Builder threshold(Double threshold); 114 | public abstract Builder events(Double events); 115 | public abstract Builder unit(@Nullable DistanceUnit unit); 116 | public abstract Builder links(Links links); 117 | 118 | public abstract OdometerTrigger build(); 119 | } 120 | 121 | @AutoParcel 122 | public static abstract class Seed{ 123 | @NonNull public abstract String vehicleId(); 124 | @NonNull public abstract TriggerType type(); 125 | @NonNull public abstract Double threshold(); 126 | @NonNull public abstract DistanceUnit unit(); 127 | 128 | /*package*/ Seed() { } 129 | 130 | @AutoParcel.Builder 131 | public static abstract class Saver{ 132 | public abstract Saver vehicleId(@NonNull String vehicleId); 133 | public abstract Saver type(@NonNull TriggerType type); 134 | public abstract Saver threshold(@NonNull Double threshold); 135 | public abstract Saver unit(@NonNull DistanceUnit unit); 136 | 137 | /*package*/ Saver() {} 138 | 139 | /*package*/ abstract Seed autoBuild(); 140 | 141 | public Observable save() { 142 | final Seed s = autoBuild(); 143 | 144 | return Vinli.curApp().distances().createOdometerTrigger(s.vehicleId(), s) 145 | .map(Wrapped.pluckItem()); 146 | } 147 | } 148 | 149 | /*package*/ static final class Adapter extends TypeAdapter { 150 | private Gson gson; 151 | 152 | @Override public void write(JsonWriter out, Seed value) throws IOException { 153 | if (gson == null) { 154 | gson = Vinli.curApp().gson(); 155 | } 156 | 157 | out.beginObject(); 158 | out.name("odometerTrigger").beginObject(); 159 | out.name("type").value(value.type().getTriggerTypeStr()); 160 | out.name("threshold").value(value.threshold()); 161 | out.name("unit").value(value.unit().getDistanceUnitStr()); 162 | out.endObject(); 163 | out.endObject(); 164 | } 165 | 166 | @Override public Seed read(JsonReader in) throws IOException { 167 | throw new UnsupportedOperationException("reading a OdometerTriggerSeed is not supported"); 168 | } 169 | } 170 | } 171 | 172 | private static final class OdometerTriggerAdapter extends TypeAdapter { 173 | private Gson gson; 174 | 175 | @Override public void write(JsonWriter out, OdometerTrigger value) throws IOException { 176 | throw new UnsupportedOperationException("writing an OdometerTrigger is not supported"); 177 | } 178 | 179 | @Override public OdometerTrigger read(JsonReader in) throws IOException { 180 | if (gson == null) { 181 | gson = Vinli.curApp().gson(); 182 | } 183 | 184 | final OdometerTrigger.Builder b = new AutoParcel_OdometerTrigger.Builder(); 185 | 186 | in.beginObject(); 187 | while (in.hasNext()) { 188 | final String name = in.nextName(); 189 | 190 | switch (name) { 191 | case "id": b.id(in.nextString()); break; 192 | case "vehicleId": b.vehicleId(in.nextString()); break; 193 | case "type": b.type(TriggerType.getEnumFromString(in.nextString())); break; 194 | case "threshold": b.threshold(in.nextDouble()); break; 195 | case "events": b.events(in.nextDouble()); break; 196 | case "unit": b.unit(DistanceUnit.parse(in.nextString())); break; 197 | case "links": b.links(gson.fromJson(in, Links.class)); break; 198 | default: in.skipValue(); break; 199 | } 200 | } 201 | in.endObject(); 202 | 203 | return b.build(); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /android-net/src/main/java/li/vin/net/TimeSeries.java: -------------------------------------------------------------------------------- 1 | package li.vin.net; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.google.gson.Gson; 8 | import com.google.gson.GsonBuilder; 9 | import com.google.gson.TypeAdapter; 10 | import com.google.gson.stream.JsonReader; 11 | import com.google.gson.stream.JsonWriter; 12 | 13 | import java.io.IOException; 14 | import java.lang.reflect.Type; 15 | import java.util.ArrayList; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.Locale; 19 | 20 | import auto.parcel.AutoParcel; 21 | import rx.Observable; 22 | import rx.functions.Func1; 23 | import rx.internal.operators.OnSubscribeFromIterable; 24 | 25 | @AutoParcel 26 | public abstract class TimeSeries implements Parcelable { 27 | /*package*/ static final Func1 EXTRACT_ITEMS = new Func1, Observable>() { 28 | @Override public Observable call(TimeSeries tTimeSeries) { 29 | return tTimeSeries.observeItems(); 30 | } 31 | }; 32 | 33 | @SuppressWarnings("unchecked") 34 | public static final Func1, Observable> extractItems() { 35 | return (Func1, Observable>) EXTRACT_ITEMS; 36 | } 37 | 38 | /*package*/ static final Func1 ALL_ITEMS = new Func1, Observable>() { 39 | @Override 40 | @SuppressWarnings("unchecked") 41 | public Observable call(TimeSeries tTimeSeries) { 42 | if (tTimeSeries.hasPrior()) { 43 | return tTimeSeries.observeItems().concatWith(tTimeSeries.loadPrior().flatMap(ALL_ITEMS)); 44 | }else if(tTimeSeries.hasNext()){ 45 | return tTimeSeries.observeItems().concatWith(tTimeSeries.loadNext().flatMap(ALL_ITEMS)); 46 | } 47 | 48 | return tTimeSeries.observeItems(); 49 | } 50 | }; 51 | 52 | @SuppressWarnings("unchecked") 53 | public static final Func1, Observable> allItems() { 54 | return (Func1, Observable>) ALL_ITEMS; 55 | } 56 | 57 | /*package*/ static final void registerGson(GsonBuilder gb) { 58 | gb.registerTypeAdapter( 59 | Meta.class, 60 | AutoParcelAdapter.create(AutoParcel_TimeSeries_Meta.class)); 61 | 62 | gb.registerTypeAdapter( 63 | Meta.Pagination.class, 64 | AutoParcelAdapter.create(AutoParcel_TimeSeries_Meta_Pagination.class)); 65 | 66 | gb.registerTypeAdapter( 67 | Meta.Pagination.Links.class, 68 | AutoParcelAdapter.create(AutoParcel_TimeSeries_Meta_Pagination_Links.class)); 69 | } 70 | 71 | /*package*/ abstract List items(); 72 | /*package*/ abstract Meta meta(); 73 | /*package*/ abstract Type type(); 74 | /*package*/ abstract String className(); 75 | 76 | private Observable> loadLink(@NonNull String link){ 77 | final Class clz; 78 | try { 79 | //noinspection unchecked 80 | clz = (Class) Class.forName(className()); 81 | } catch (ClassNotFoundException e) { 82 | throw new RuntimeException(e); 83 | } 84 | 85 | //noinspection unchecked 86 | return (Observable>) Vinli.curApp().pagingTsObservable(clz, link); 87 | } 88 | 89 | public int size() { 90 | return items().size(); 91 | } 92 | 93 | public int total() { 94 | return size() + meta().pagination().remaining(); 95 | } 96 | 97 | public List getItems() { 98 | return Collections.unmodifiableList(items()); 99 | } 100 | 101 | public Observable observeItems() { 102 | return Observable.create(new OnSubscribeFromIterable<>(items())); 103 | } 104 | 105 | public Observable> loadPrior() { 106 | final Meta.Pagination.Links links = meta().pagination().links(); 107 | if (links == null) { 108 | return Observable.error(new IOException("no links")); 109 | } 110 | 111 | final String link = links.prior(); 112 | if (link == null) { 113 | return Observable.error(new IOException("no prior link")); 114 | } 115 | 116 | return loadLink(link); 117 | } 118 | 119 | public Observable> loadNext(){ 120 | final Meta.Pagination.Links links = meta().pagination().links(); 121 | if(links == null){ 122 | return Observable.error(new IOException("no links")); 123 | } 124 | 125 | final String link = links.next(); 126 | if(link == null){ 127 | return Observable.error(new IOException("no next link")); 128 | } 129 | 130 | return loadLink(link); 131 | } 132 | 133 | public boolean hasPrior() { 134 | final Meta.Pagination.Links links = meta().pagination().links(); 135 | return links != null && links.prior() != null; 136 | } 137 | 138 | public boolean hasNext(){ 139 | final Meta.Pagination.Links links = meta().pagination().links(); 140 | return links != null && links.next() != null; 141 | } 142 | 143 | /*package*/ TimeSeries() { } 144 | 145 | @AutoParcel 146 | /*package*/ static abstract class Meta implements Parcelable { 147 | public abstract Pagination pagination(); 148 | 149 | @AutoParcel 150 | public static abstract class Pagination implements Parcelable { 151 | public abstract int remaining(); 152 | public abstract int limit(); 153 | public abstract String until(); 154 | @Nullable public abstract Links links(); 155 | 156 | @AutoParcel 157 | public static abstract class Links implements Parcelable { 158 | @Nullable public abstract String prior(); 159 | @Nullable public abstract String next(); 160 | } 161 | } 162 | } 163 | 164 | @AutoParcel.Builder 165 | /*package*/ interface Builder { 166 | Builder items(List l); 167 | Builder meta(Meta m); 168 | Builder type(Type t); 169 | Builder className(String c); 170 | 171 | TimeSeries build(); 172 | } 173 | 174 | /*package*/ static final class Adapter extends TypeAdapter> { 175 | public static final Adapter create(Type pageType, Class itemCls) { 176 | return create(pageType, itemCls, itemCls.getSimpleName().toLowerCase(Locale.US) + 's'); 177 | } 178 | 179 | public static final Adapter create(Type pageType, Class itemCls, String collectionName) { 180 | return new Adapter<>(pageType, itemCls, collectionName); 181 | } 182 | 183 | private final Type pageType; 184 | private final Class itemCls; 185 | private final String collectionName; 186 | 187 | private Gson gson; 188 | 189 | private Adapter(Type pageType, Class itemCls, String collectionName) { 190 | this.pageType = pageType; 191 | this.itemCls = itemCls; 192 | this.collectionName = collectionName; 193 | } 194 | 195 | @Override public void write(JsonWriter out, TimeSeries value) throws IOException { 196 | throw new UnsupportedOperationException("writing a time series is not supported"); 197 | } 198 | 199 | @Override public TimeSeries read(JsonReader in) throws IOException { 200 | if (gson == null) { 201 | gson = Vinli.curApp().gson(); 202 | } 203 | 204 | final TimeSeries.Builder b = new AutoParcel_TimeSeries.Builder() 205 | .type(pageType) 206 | .className(itemCls.getName()); 207 | 208 | in.beginObject(); 209 | while (in.hasNext()) { 210 | final String name = in.nextName(); 211 | 212 | if ("meta".equals(name)) { 213 | b.meta(gson.fromJson(in, TimeSeries.Meta.class)); 214 | } else if (collectionName.equals(name)) { 215 | final List items = new ArrayList<>(); 216 | 217 | in.beginArray(); 218 | while (in.hasNext()) { 219 | items.add(gson.fromJson(in, itemCls)); 220 | } 221 | in.endArray(); 222 | 223 | b.items(items); 224 | } else { 225 | throw new IOException("unrecognized key '" + name + "' while parsing " + collectionName); 226 | } 227 | } 228 | in.endObject(); 229 | 230 | return b.build(); 231 | } 232 | } 233 | } 234 | --------------------------------------------------------------------------------