diff --git a/app/build.gradle b/app/build.gradle
index 60eb309..9380bf0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,7 +8,7 @@ android {
minSdkVersion 19
targetSdkVersion 29
versionCode 1
- versionName "1.0"
+ versionName "1.1"
}
buildTypes {
release {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2235adb..43b4308 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,6 +28,7 @@
android:name="com.google.android.glass.VoiceTrigger"
android:resource="@xml/voice_trigger" />
+
\ No newline at end of file
diff --git a/app/src/main/java/haus/nightmare/GlassTesla/MainActivity.java b/app/src/main/java/haus/nightmare/GlassTesla/MainActivity.java
index 578430f..a2929a4 100644
--- a/app/src/main/java/haus/nightmare/GlassTesla/MainActivity.java
+++ b/app/src/main/java/haus/nightmare/GlassTesla/MainActivity.java
@@ -1,63 +1,85 @@
package haus.nightmare.GlassTesla;
-import com.google.android.glass.media.Sounds;
-import com.google.android.glass.widget.CardBuilder;
-import com.google.android.glass.widget.CardScrollAdapter;
-import com.google.android.glass.widget.CardScrollView;
-import com.google.gson.Gson;
-
+import android.annotation.SuppressLint;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.media.AudioManager;
+import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.StrictMode;
-import android.util.Log;
+import android.os.IBinder;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
-import org.conscrypt.Conscrypt;
+import com.google.android.glass.media.Sounds;
+import com.google.android.glass.widget.CardBuilder;
+import com.google.android.glass.widget.CardScrollAdapter;
+import com.google.android.glass.widget.CardScrollView;
-import java.io.IOException;
import java.net.URL;
-import java.security.Security;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
-import javax.net.ssl.SSLException;
-
-import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
-import okio.BufferedSink;
public class MainActivity extends Activity {
+ private static final String TAG = "GT-Restart";
private CardScrollView cardScroller;
private TeslaResponse vehicleData;
private SharedPreferences prefs;
- private ArrayList scrollerViews = new ArrayList();
+ private ArrayList scrollerViews;
+
+ private SyncTeslaService mService;
+ private boolean mBound = false;
+
+ @SuppressLint("SimpleDateFormat")
+ private SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm");
+ /** Defines callbacks for service binding, passed to bindService() */
+ private final ServiceConnection connection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance
+ SyncTeslaService.SyncTeslaBinder binder = (SyncTeslaService.SyncTeslaBinder) service;
+ mService = binder.getService();
+ mBound = true;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ mBound = false;
+ }
+ };
@Override
- protected void onCreate(Bundle bundle) {
+ protected void onCreate(final Bundle bundle) {
super.onCreate(bundle);
- StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
- StrictMode.setThreadPolicy(policy);
prefs = getPreferences(MODE_PRIVATE);
- scrollerViews.add(buildStatusView());
+ Intent intent = new Intent(this, SyncTeslaService.class);
+ startService(intent);
+ bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ scrollerViews = new ArrayList();
+ cardScroller = new CardScrollView(getApplicationContext());
+
+ scrollerViews.add(buildLoadingView());
scrollerViews.add(buildLocksView());
scrollerViews.add(buildControlView());
- cardScroller = new CardScrollView(this);
cardScroller.setAdapter(new CardScrollAdapter() {
@Override
public int getCount() {
@@ -83,97 +105,156 @@ public class MainActivity extends Activity {
cardScroller.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
-
- if (position == 2) {
- postCommand("flash_lights");
- }
-
- if (position == 1) {
- if (vehicleData.vehicle_state.locked) {
- postCommand("door_unlock");
- } else {
- postCommand("door_lock");
- }
- scrollerViews.set(1, buildLocksView());
- }
-
- if (position == 0) {
- scrollerViews.set(0, buildStatusView());
+ switch(position) {
+ case 2:
+ postCommand("flash_lights");
+ break;
+ case 1:
+ if (vehicleData.vehicle_state.locked) {
+ postCommand("door_unlock");
+ } else {
+ postCommand("door_lock");
+ }
+ break;
+ default:
+ updateIfExpired();
}
-
- AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- am.playSoundEffect(Sounds.TAP);
-
}
-
});
cardScroller.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
-
- if (position == 2) {
- return postCommand("honk_horn");
- }
- if (position == 1) {
- return postCommand("set_sentry_mode?on=" + !vehicleData.vehicle_state.sentry_mode);
- }
- if (position == 0) {
- return postCommand("remote_start_drive?password=" + prefs.getString("auth_password", ""));
+ switch (position) {
+ case 2:
+ postCommand("honk_horn");
+ break;
+ case 1:
+ postCommand("set_sentry_mode?on=" +
+ !vehicleData.vehicle_state.sentry_mode);
+ break;
+ default:
+ postCommand("remote_start_drive?password=" + prefs.getString("auth_password", ""));
}
- AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- am.playSoundEffect(Sounds.DISALLOWED);
- return false;
+ return true;
}
});
setContentView(cardScroller);
}
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Intent intent = new Intent(this, SyncTeslaService.class);
+ bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ unbindService(connection);
+ mBound = false;
+ }
+
@Override
protected void onResume() {
super.onResume();
- cardScroller.activate();
+ if (this.cardScroller != null) {
+ cardScroller.activate();
+ } else {
+ updateIfExpired();
+ }
+
+ }
+
+ private void updateIfExpired() {
+ if (mBound) {
+ Calendar expire = Calendar.getInstance();
+ expire.add(Calendar.MINUTE, -5);
+ vehicleData = mService.getVehicleData();
+ if (vehicleData == null || new Date(vehicleData.vehicle_state.timestamp).after(expire.getTime())) {
+ refreshCards();
+ } else {
+ updateCards();
+ }
+ } else {
+ refreshCards();
+ }
}
@Override
protected void onPause() {
- cardScroller.deactivate();
+ if (cardScroller != null) {
+ cardScroller.deactivate();
+ }
super.onPause();
}
+
+ private void playAudio(int sound) {
+ AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (am != null) {
+ am.playSoundEffect(sound);
+ }
+ }
+
+ private void refreshCards() {
+ Thread t = new Thread(new Runnable(){
+ public void run() {
+ mService.updateSharedPrefs(prefs);
+ mService.loadVehicleData(prefs.getString("vehicle_id", ""));
+ vehicleData = mService.getVehicleData();
+ }
+ });
+ t.start();
+ try {
+ t.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ updateCards();
+ }
+ private void updateCards() {
+ scrollerViews = new ArrayList();
+ scrollerViews.add(buildStatusView());
+ scrollerViews.add(buildLocksView());
+ scrollerViews.add(buildControlView());
+ cardScroller.animate(0, CardScrollView.Animation.INSERTION);
+ }
+
private View buildControlView() {
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT);
card.setText("Tap to flash lights, long press to honk.");
return card.getView();
}
- private View buildLocksView() {
- updateIfNeeded();
+ private View buildLoadingView() {
+ CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT);
+ card.setText("Tap to load data.");
+ return card.getView();
+ }
+ private View buildLocksView() {
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT);
- String body = vehicleData.vehicle_state.locked ? "Tap to unlock" : "Tap to lock";
+ String body;
+ if (vehicleData == null) {
+ body = "Tap to lock";
+ } else {
+ body = "Tap to " + (vehicleData.vehicle_state.locked ? "unlock":"lock");
+ }
body += "\nLong press to ";
- body += vehicleData.vehicle_state.sentry_mode ? "disable" : "enable";
+ if (vehicleData == null) {
+ body += "enable";
+ } else {
+ body += vehicleData.vehicle_state.sentry_mode ? "disable" : "enable";
+ }
+
body += " Sentry Mode";
card.setText(body);
return card.getView();
}
- private void updateIfNeeded() {
- Calendar expire = Calendar.getInstance();
- expire.add(Calendar.MINUTE, -5);
- if (vehicleData == null || new Date(vehicleData.vehicle_state.timestamp).after(expire.getTime())) {
- try {
- getVehicleData();
- } catch(Exception e) {
- Log.v("Tesla API Setup", "Unable to get Tesla data", e);
- this.finishAffinity();
- System.exit(0); // Shouldn't be reached, but just in case let's kill it dead
- }
- }
- }
private View buildStatusView() {
- updateIfNeeded();
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT);
String chargeDuration = "";
@@ -186,6 +267,9 @@ public class MainActivity extends Activity {
if (minutes > 0) {
chargeDuration += minutes + "m";
}
+ if (vehicleData.charge_state.charging_state.equalsIgnoreCase("Disconnected")) {
+ chargeDuration = "N/A";
+ }
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR_OF_DAY, hours);
cal.add(Calendar.MINUTE, minutes);
@@ -197,92 +281,73 @@ public class MainActivity extends Activity {
vehicleData.charge_state.charging_state,
vehicleData.charge_state.charger_power,
chargeDuration,
- new SimpleDateFormat("hh:mm").format(cal.getTime())
+ timeFormatter.format(cal.getTime())
)
);
card.setFootnote(String.format("%s last updated: %s",
vehicleData.display_name,
- new SimpleDateFormat("hh:mm").format(vehicleData.vehicle_state.timestamp)
+ timeFormatter.format(vehicleData.vehicle_state.timestamp)
));
View v = card.getView();
v.setKeepScreenOn(true);
return v;
}
- // /api/1/vehicles/{id}/vehicle_data
- public void getVehicleData() {
- String vehicleId = prefs.getString("vehicle_id", "");
- String json = getResponseFromJsonURL("https://owner-api.teslamotors.com/api/1/vehicles/"
- + vehicleId +"/vehicle_data");
- Gson gson = new Gson();
- ResponseRoot root = gson.fromJson(json, ResponseRoot.class);
- vehicleData = root.response;
- }
-
-
- public boolean postCommand(String command) {
- try {
- String token = prefs.getString("bearer_token", "");
- String vehicleId = prefs.getString("vehicle_id", "");
- URL object = new URL("https://owner-api.teslamotors.com/api/1/vehicles/"
- + vehicleId +"/command/" + command);
- OkHttpClient client = new OkHttpClient();
- Request request = new Request.Builder()
- .url(object)
- .method("POST", RequestBody.create(null, new byte[]{}))
- .addHeader("Authorization", "Bearer " + token)
- .addHeader("User-Agent", "Tesla-GLASS")
- .addHeader("Content-Type", "application/json")
- .build();
-
-
- Response response = client.newCall(request).execute();
-
- return response.code() == 200;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return false;
- }
-
- public String getResponseFromJsonURL(String url) {
- String token = prefs.getString("bearer_token", "");
-
- String jsonResponse = null;
- if (url.length() > 0) {
- try {
- Security.insertProviderAt(Conscrypt.newProvider(), 1);
+ public void postCommand(String command) {
- /************** For getting response from HTTP URL start ***************/
- URL object = new URL(url);
+ AsyncTask, Boolean> task = new AsyncTask, Boolean>() {
- OkHttpClient client = new OkHttpClient();
- Request request = new Request.Builder()
- .url(object)
- .addHeader("Authorization", "Bearer " + token)
- .addHeader("User-Agent", "Tesla-GLASS")
- .addHeader("Content-Type", "application/json")
- .build();
-
- Response response = client.newCall(request).execute();
-
- int responseCode = response.code();
+ @Override
+ protected Boolean doInBackground(String... commands) {
+ String command = commands[0];
+ try {
+ String token = prefs.getString("bearer_token", "");
+ String vehicleId = prefs.getString("vehicle_id", "");
+ URL object = new URL("https://owner-api.teslamotors.com/api/1/vehicles/"
+ + vehicleId +"/command/" + command);
+ OkHttpClient client = new OkHttpClient();
+ Request request = new Request.Builder()
+ .url(object)
+ .method("POST", RequestBody.create(null, new byte[]{}))
+ .addHeader("Authorization", "Bearer " + token)
+ .addHeader("User-Agent", "Tesla-GLASS")
+ .addHeader("Content-Type", "application/json")
+ .build();
+
+
+ Response response = client.newCall(request).execute();
+ if (response.code() == 408) {
+ postCommand("wake_up");
+ }
+ return response.code() == 200;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
- if (responseCode == 200) {
- return response.body().string();
+ @Override
+ protected void onPreExecute() {
+ cardScroller.animate(0, CardScrollView.Animation.NAVIGATION);
+ }
+ protected void onPostExecute(Boolean result) {
+ if (result) {
+ playAudio(Sounds.SUCCESS);
+ } else {
+ playAudio(Sounds.DISALLOWED);
}
- } catch (SSLException e) {
- Log.e("DataSSL", "SSL Exception getting json:", e);
- } catch (Exception e) {
- e.printStackTrace();
}
- }
- return jsonResponse;
+ };
+ task.execute(command);
+
}
+
+
}
+
diff --git a/app/src/main/java/haus/nightmare/GlassTesla/SyncTeslaService.java b/app/src/main/java/haus/nightmare/GlassTesla/SyncTeslaService.java
new file mode 100644
index 0000000..f718bfc
--- /dev/null
+++ b/app/src/main/java/haus/nightmare/GlassTesla/SyncTeslaService.java
@@ -0,0 +1,171 @@
+package haus.nightmare.GlassTesla;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import com.google.gson.Gson;
+import org.conscrypt.Conscrypt;
+import java.net.URL;
+import java.security.Security;
+import javax.net.ssl.SSLException;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+public class SyncTeslaService extends Service {
+ int startMode; // indicates how to behave if the service is killed
+ IBinder binder = new SyncTeslaBinder(); // interface for clients that bind
+ boolean allowRebind; // indicates whether onRebind should be used
+ private TeslaResponse vehicleData;
+ private SharedPreferences prefs;
+
+
+ public class SyncTeslaBinder extends Binder {
+ SyncTeslaService getService() {
+ return SyncTeslaService.this;
+ }
+ }
+
+ public TeslaResponse getVehicleData() {
+ return vehicleData;
+ }
+
+ @Override
+ public void onCreate() {
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // The service is starting, due to a call to startService()
+
+ new Thread(new Runnable(){
+ public void run() {
+ // TODO Auto-generated method stub
+ while(true)
+ {
+ try {
+ Thread.sleep(2 * 60000);
+ loadVehicleData();
+
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ }
+ }).start();
+
+ return startMode;
+ }
+ public void loadVehicleData(String vehicleId) {
+ String json = getResponseFromJsonURL("https://owner-api.teslamotors.com/api/1/vehicles/"
+ + vehicleId +"/vehicle_data");
+ Gson gson = new Gson();
+ ResponseRoot root = gson.fromJson(json, ResponseRoot.class);
+ vehicleData = root.response;
+ }
+ public void loadVehicleData() {
+ loadVehicleData(prefs.getString("vehicle_id", ""));
+ }
+
+ public void updateSharedPrefs(SharedPreferences p) {
+ SharedPreferences.Editor e = prefs.edit();
+ prefs = p;
+ e.putString("bearer_token", p.getString("bearer_token", ""));
+ e.putString("vehicle_id", p.getString("vehicle_id", ""));
+ e.apply();
+
+ }
+
+ public boolean postCommand(String command) {
+ try {
+ String token = prefs.getString("bearer_token", "");
+ String vehicleId = prefs.getString("vehicle_id", "");
+ URL object = new URL("https://owner-api.teslamotors.com/api/1/vehicles/"
+ + vehicleId +"/command/" + command);
+ OkHttpClient client = new OkHttpClient();
+ Request request = new Request.Builder()
+ .url(object)
+ .method("POST", RequestBody.create(null, new byte[]{}))
+ .addHeader("Authorization", "Bearer " + token)
+ .addHeader("User-Agent", "Tesla-GLASS")
+ .addHeader("Content-Type", "application/json")
+ .build();
+
+
+ Response response = client.newCall(request).execute();
+ if (response.code() == 408) {
+ postCommand("wake_up");
+ }
+ return response.code() == 200;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ public String getResponseFromJsonURL(String url) {
+ String token = prefs.getString("bearer_token", "");
+
+ String jsonResponse = null;
+ if (url.length() > 0) {
+ try {
+ Security.insertProviderAt(Conscrypt.newProvider(), 1);
+ /************** For getting response from HTTP URL start ***************/
+ URL object = new URL(url);
+
+ OkHttpClient client = new OkHttpClient();
+ Request request = new Request.Builder()
+ .url(object)
+ .addHeader("Authorization", "Bearer " + token)
+ .addHeader("User-Agent", "Tesla-GLASS")
+ .addHeader("Content-Type", "application/json")
+ .build();
+
+ Response response = client.newCall(request).execute();
+
+ int responseCode = response.code();
+
+ if (responseCode == 200) {
+ return response.body().string();
+
+ } else if (responseCode == 408) {
+ postCommand("wake_up");
+ }
+ } catch (SSLException e) {
+ Log.e("DataSSL", "SSL Exception getting json:", e);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return jsonResponse;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // A client is binding to the service with bindService()
+ return binder;
+ }
+ @Override
+ public boolean onUnbind(Intent intent) {
+ // All clients have unbound with unbindService()
+ return allowRebind;
+ }
+ @Override
+ public void onRebind(Intent intent) {
+ // A client is binding to the service with bindService(),
+ // after onUnbind() has already been called
+ }
+ @Override
+ public void onDestroy() {
+ // The service is no longer used and is being destroyed
+ }
+}
\ No newline at end of file