diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java index d068431..91b3f5c 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java @@ -18,6 +18,13 @@ import android.os.IBinder; import android.os.Looper; import androidx.core.app.NotificationCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.work.BackoffPolicy; +import androidx.work.Constraints; +import androidx.work.Data; +import androidx.work.ExistingWorkPolicy; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; import android.util.Log; @@ -27,6 +34,8 @@ import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.LocationServices; +import java.util.concurrent.TimeUnit; + /** * A bound and started service that is promoted to a foreground service when location updates have * been requested and all clients unbind. @@ -74,6 +83,8 @@ public class LocationUpdatesService extends Service { private LocationRequest mLocationRequest; + private long requestInterval = 90000; + /** * Provides access to the Fused Location Provider API. */ @@ -116,10 +127,10 @@ public class LocationUpdatesService extends Service { // Android O requires a Notification Channel. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = "Location service"; + CharSequence name = "Location updates"; // Create the channel for the notification NotificationChannel mChannel = - new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT); + new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW); // Set the Notification Channel for the Notification Manager. mNotificationManager.createNotificationChannel(mChannel); @@ -138,7 +149,7 @@ public class LocationUpdatesService extends Service { stopSelf(); } // Tells the system to not try to recreate the service after it has been killed. - return START_NOT_STICKY; + return START_STICKY; } @Override @@ -188,7 +199,8 @@ public class LocationUpdatesService extends Service { } public void requestLocationUpdates() { - Log.i(TAG, "Requesting location updates"); + requestInterval = Utils.getLocationUpdateIntervals(getApplicationContext()); + Log.i(TAG, "Requesting location updates. Interval is " + requestInterval); Utils.setRequestingLocationUpdates(this, true); startService(new Intent(getApplicationContext(), LocationUpdatesService.class)); try { @@ -237,6 +249,7 @@ public class LocationUpdatesService extends Service { .addAction(R.drawable.blank_icon, "Stop", servicePendingIntent) .setContentText(text) + .setPriority(-1) .setContentTitle(Utils.getLocationTitle(mLocation)) .setOngoing(true) .setSmallIcon(R.mipmap.ic_launcher) @@ -274,16 +287,41 @@ public class LocationUpdatesService extends Service { if (serviceIsRunningInForeground(this)) { mNotificationManager.notify(NOTIFICATION_ID, getNotification()); } + + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build(); + + // Create the Data object: + Data locationData = new Data.Builder() + .putDouble("Lat", mLocation.getLatitude()) + .putDouble("Long", mLocation.getLongitude()) + .putFloat("Acc", mLocation.getAccuracy()) + .build(); + + + OneTimeWorkRequest uploadWorkRequest = + new OneTimeWorkRequest.Builder(SendLocationWorker.class) + .setBackoffCriteria( + BackoffPolicy.EXPONENTIAL, + 10, + TimeUnit.SECONDS) + .setConstraints(constraints) + .setInputData(locationData) + .build(); + + WorkManager + .getInstance(getApplicationContext()) + .enqueueUniqueWork("SendLocationUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest); } /** * Sets the location request parameters. */ private void createLocationRequest() { - long interval = Utils.getLocationUpdateIntervals(getApplicationContext()); mLocationRequest = new LocationRequest(); - mLocationRequest.setInterval(interval); - mLocationRequest.setFastestInterval(interval); + mLocationRequest.setInterval(requestInterval); + mLocationRequest.setFastestInterval(requestInterval); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendLocationWorker.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendLocationWorker.java new file mode 100644 index 0000000..2cc79f1 --- /dev/null +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendLocationWorker.java @@ -0,0 +1,98 @@ +package com.keyboardcrumbs.hassclient; + +import android.app.AlarmManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; +import android.webkit.URLUtil; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +public class SendLocationWorker extends Worker { + + private Context currentContext; + private static final String TAG = "SendLocationWorker"; + + public static final String KEY_LAT_ARG = "Lat"; + public static final String KEY_LONG_ARG = "Long"; + public static final String KEY_ACC_ARG = "Acc"; + + public SendLocationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + currentContext = context; + } + + @NonNull + @Override + public Result doWork() { + SharedPreferences prefs = currentContext.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE); + String webhookId = prefs.getString("flutter.app-webhook-id", null); + if (webhookId != null) { + try { + String requestUrl = prefs.getString("flutter.hassio-res-protocol", "") + + "://" + + prefs.getString("flutter.hassio-domain", "") + + ":" + + prefs.getString("flutter.hassio-port", "") + "/api/webhook/" + webhookId; + JSONObject dataToSend = new JSONObject(); + if (URLUtil.isValidUrl(requestUrl)) { + dataToSend.put("type", "update_location"); + JSONObject dataObject = new JSONObject(); + + JSONArray gps = new JSONArray(); + gps.put(0, getInputData().getDouble(KEY_LAT_ARG, 0)); + gps.put(1, getInputData().getDouble(KEY_LONG_ARG, 0)); + + dataObject.put("gps", gps); + dataObject.put("gps_accuracy", getInputData().getFloat(KEY_ACC_ARG, 0)); + dataObject.put("battery", 41); + + dataToSend.put("data", dataObject); + + String stringRequest = dataToSend.toString(); + try { + URL url = new URL(requestUrl); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setRequestMethod("POST"); + urlConnection.setRequestProperty("Content-Type", "application/json"); + urlConnection.setDoOutput(true); + byte[] outputBytes = stringRequest.getBytes("UTF-8"); + OutputStream os = urlConnection.getOutputStream(); + os.write(outputBytes); + + int responseCode = urlConnection.getResponseCode(); + urlConnection.disconnect(); + if (responseCode >= 300) { + return Result.retry(); + } + } catch (Exception e) { + Log.e(TAG, "Error sending data", e); + return Result.retry(); + } + } else { + Log.w(TAG, "Invalid HA url"); + return Result.failure(); + } + } catch (Exception e) { + Log.e(TAG, "Error =(", e); + return Result.failure(); + } + } else { + Log.w(TAG, "Webhook id not found"); + return Result.failure(); + } + return Result.success(); + } +}