Browse Source

Begin pushing networking to Service class

master
Gregory Rudolph 2 years ago
parent
commit
a371a3f0c4
Signed by: rudi
GPG Key ID: EF64F3CBD1A1EBDD
  1. 2
      app/build.gradle
  2. 1
      app/src/main/AndroidManifest.xml
  3. 345
      app/src/main/java/haus/nightmare/GlassTesla/MainActivity.java
  4. 171
      app/src/main/java/haus/nightmare/GlassTesla/SyncTeslaService.java

2
app/build.gradle

@ -8,7 +8,7 @@ android {
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 29 targetSdkVersion 29
versionCode 1 versionCode 1
versionName "1.0" versionName "1.1"
} }
buildTypes { buildTypes {
release { release {

1
app/src/main/AndroidManifest.xml

@ -28,6 +28,7 @@
android:name="com.google.android.glass.VoiceTrigger" android:name="com.google.android.glass.VoiceTrigger"
android:resource="@xml/voice_trigger" /> android:resource="@xml/voice_trigger" />
</activity> </activity>
<service android:name="SyncTeslaService" />
</application> </application>
</manifest> </manifest>

345
app/src/main/java/haus/nightmare/GlassTesla/MainActivity.java

@ -1,63 +1,85 @@
package haus.nightmare.GlassTesla; package haus.nightmare.GlassTesla;
import com.google.android.glass.media.Sounds; import android.annotation.SuppressLint;
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.app.Activity; import android.app.Activity;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.StrictMode; import android.os.IBinder;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; 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.net.URL;
import java.security.Security;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import javax.net.ssl.SSLException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import okio.BufferedSink;
public class MainActivity extends Activity { public class MainActivity extends Activity {
private static final String TAG = "GT-Restart";
private CardScrollView cardScroller; private CardScrollView cardScroller;
private TeslaResponse vehicleData; private TeslaResponse vehicleData;
private SharedPreferences prefs; private SharedPreferences prefs;
private ArrayList<View> scrollerViews = new ArrayList<View>(); private ArrayList<View> 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 @Override
protected void onCreate(Bundle bundle) { protected void onCreate(final Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
prefs = getPreferences(MODE_PRIVATE); 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<View>();
cardScroller = new CardScrollView(getApplicationContext());
scrollerViews.add(buildLoadingView());
scrollerViews.add(buildLocksView()); scrollerViews.add(buildLocksView());
scrollerViews.add(buildControlView()); scrollerViews.add(buildControlView());
cardScroller = new CardScrollView(this);
cardScroller.setAdapter(new CardScrollAdapter() { cardScroller.setAdapter(new CardScrollAdapter() {
@Override @Override
public int getCount() { public int getCount() {
@ -83,97 +105,156 @@ public class MainActivity extends Activity {
cardScroller.setOnItemClickListener(new AdapterView.OnItemClickListener() { cardScroller.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch(position) {
if (position == 2) { case 2:
postCommand("flash_lights"); postCommand("flash_lights");
} break;
case 1:
if (position == 1) { if (vehicleData.vehicle_state.locked) {
if (vehicleData.vehicle_state.locked) { postCommand("door_unlock");
postCommand("door_unlock"); } else {
} else { postCommand("door_lock");
postCommand("door_lock"); }
} break;
scrollerViews.set(1, buildLocksView()); default:
} updateIfExpired();
if (position == 0) {
scrollerViews.set(0, buildStatusView());
} }
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.playSoundEffect(Sounds.TAP);
} }
}); });
cardScroller.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { cardScroller.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override @Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
if (position == 2) { case 2:
return postCommand("honk_horn"); postCommand("honk_horn");
} break;
if (position == 1) { case 1:
return postCommand("set_sentry_mode?on=" + !vehicleData.vehicle_state.sentry_mode); postCommand("set_sentry_mode?on=" +
} !vehicleData.vehicle_state.sentry_mode);
if (position == 0) { break;
return postCommand("remote_start_drive?password=" + prefs.getString("auth_password", "")); default:
postCommand("remote_start_drive?password=" + prefs.getString("auth_password", ""));
} }
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); return true;
am.playSoundEffect(Sounds.DISALLOWED);
return false;
} }
}); });
setContentView(cardScroller); 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 @Override
protected void onResume() { protected void onResume() {
super.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 @Override
protected void onPause() { protected void onPause() {
cardScroller.deactivate(); if (cardScroller != null) {
cardScroller.deactivate();
}
super.onPause(); 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<View>();
scrollerViews.add(buildStatusView());
scrollerViews.add(buildLocksView());
scrollerViews.add(buildControlView());
cardScroller.animate(0, CardScrollView.Animation.INSERTION);
}
private View buildControlView() { private View buildControlView() {
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT); CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT);
card.setText("Tap to flash lights, long press to honk."); card.setText("Tap to flash lights, long press to honk.");
return card.getView(); return card.getView();
} }
private View buildLocksView() { private View buildLoadingView() {
updateIfNeeded(); 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); 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 += "\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"; body += " Sentry Mode";
card.setText(body); card.setText(body);
return card.getView(); 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() { private View buildStatusView() {
updateIfNeeded();
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT); CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT);
String chargeDuration = ""; String chargeDuration = "";
@ -186,6 +267,9 @@ public class MainActivity extends Activity {
if (minutes > 0) { if (minutes > 0) {
chargeDuration += minutes + "m"; chargeDuration += minutes + "m";
} }
if (vehicleData.charge_state.charging_state.equalsIgnoreCase("Disconnected")) {
chargeDuration = "N/A";
}
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR_OF_DAY, hours); cal.add(Calendar.HOUR_OF_DAY, hours);
cal.add(Calendar.MINUTE, minutes); cal.add(Calendar.MINUTE, minutes);
@ -197,92 +281,73 @@ public class MainActivity extends Activity {
vehicleData.charge_state.charging_state, vehicleData.charge_state.charging_state,
vehicleData.charge_state.charger_power, vehicleData.charge_state.charger_power,
chargeDuration, chargeDuration,
new SimpleDateFormat("hh:mm").format(cal.getTime()) timeFormatter.format(cal.getTime())
) )
); );
card.setFootnote(String.format("%s last updated: %s", card.setFootnote(String.format("%s last updated: %s",
vehicleData.display_name, vehicleData.display_name,
new SimpleDateFormat("hh:mm").format(vehicleData.vehicle_state.timestamp) timeFormatter.format(vehicleData.vehicle_state.timestamp)
)); ));
View v = card.getView(); View v = card.getView();
v.setKeepScreenOn(true); v.setKeepScreenOn(true);
return v; 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 ***************/ AsyncTask<String, ArrayList<View>, Boolean> task = new AsyncTask<String, ArrayList<View>, Boolean>() {
URL object = new URL(url);
OkHttpClient client = new OkHttpClient(); @Override
Request request = new Request.Builder() protected Boolean doInBackground(String... commands) {
.url(object) String command = commands[0];
.addHeader("Authorization", "Bearer " + token) try {
.addHeader("User-Agent", "Tesla-GLASS") String token = prefs.getString("bearer_token", "");
.addHeader("Content-Type", "application/json") String vehicleId = prefs.getString("vehicle_id", "");
.build(); URL object = new URL("https://owner-api.teslamotors.com/api/1/vehicles/"
+ vehicleId +"/command/" + command);
Response response = client.newCall(request).execute(); OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
int responseCode = response.code(); .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) { @Override
return response.body().string(); 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);
} }
} }

171
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
}
}
Loading…
Cancel
Save