Begin pushing networking to Service class
This commit is contained in:
@ -8,7 +8,7 @@ android {
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionName "1.1"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
android:name="com.google.android.glass.VoiceTrigger"
|
||||
android:resource="@xml/voice_trigger" />
|
||||
</activity>
|
||||
<service android:name="SyncTeslaService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -1,63 +1,85 @@
|
||||
package haus.nightmare.GlassTesla;
|
||||
|
||||
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.IBinder;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
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.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
|
||||
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<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
|
||||
protected void onCreate(Bundle bundle) {
|
||||
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(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<View>();
|
||||
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) {
|
||||
switch(position) {
|
||||
case 2:
|
||||
postCommand("flash_lights");
|
||||
}
|
||||
|
||||
if (position == 1) {
|
||||
break;
|
||||
case 1:
|
||||
if (vehicleData.vehicle_state.locked) {
|
||||
postCommand("door_unlock");
|
||||
} else {
|
||||
postCommand("door_lock");
|
||||
}
|
||||
scrollerViews.set(1, buildLocksView());
|
||||
break;
|
||||
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() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
|
||||
if (position == 2) {
|
||||
return postCommand("honk_horn");
|
||||
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", ""));
|
||||
}
|
||||
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", ""));
|
||||
}
|
||||
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();
|
||||
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() {
|
||||
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<View>();
|
||||
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);
|
||||
String body = vehicleData.vehicle_state.locked ? "Tap to unlock" : "Tap to lock";
|
||||
card.setText("Tap to load data.");
|
||||
return card.getView();
|
||||
}
|
||||
|
||||
private View buildLocksView() {
|
||||
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT);
|
||||
String body;
|
||||
if (vehicleData == null) {
|
||||
body = "Tap to lock";
|
||||
} else {
|
||||
body = "Tap to " + (vehicleData.vehicle_state.locked ? "unlock":"lock");
|
||||
}
|
||||
body += "\nLong press to ";
|
||||
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,30 +281,27 @@ 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) {
|
||||
public void postCommand(String command) {
|
||||
|
||||
AsyncTask<String, ArrayList<View>, Boolean> task = new AsyncTask<String, ArrayList<View>, Boolean>() {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... commands) {
|
||||
String command = commands[0];
|
||||
try {
|
||||
String token = prefs.getString("bearer_token", "");
|
||||
String vehicleId = prefs.getString("vehicle_id", "");
|
||||
@ -237,7 +318,9 @@ public class MainActivity extends Activity {
|
||||
|
||||
|
||||
Response response = client.newCall(request).execute();
|
||||
|
||||
if (response.code() == 408) {
|
||||
postCommand("wake_up");
|
||||
}
|
||||
return response.code() == 200;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@ -245,44 +328,26 @@ public class MainActivity extends Activity {
|
||||
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();
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
cardScroller.animate(0, CardScrollView.Animation.NAVIGATION);
|
||||
}
|
||||
} catch (SSLException e) {
|
||||
Log.e("DataSSL", "SSL Exception getting json:", e);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (result) {
|
||||
playAudio(Sounds.SUCCESS);
|
||||
} else {
|
||||
playAudio(Sounds.DISALLOWED);
|
||||
}
|
||||
}
|
||||
return jsonResponse;
|
||||
};
|
||||
task.execute(command);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user