From a371a3f0c48df0d83a98e43cb295665a0c6dad7e Mon Sep 17 00:00:00 2001 From: Rudi Date: Sun, 27 Mar 2022 17:50:29 -0400 Subject: [PATCH] Begin pushing networking to Service class --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 1 + .../nightmare/GlassTesla/MainActivity.java | 345 +++++++++++------- .../GlassTesla/SyncTeslaService.java | 171 +++++++++ 4 files changed, 378 insertions(+), 141 deletions(-) create mode 100644 app/src/main/java/haus/nightmare/GlassTesla/SyncTeslaService.java 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