diff --git a/android/app/build.gradle b/android/app/build.gradle index 6215bc3..c30904e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -81,6 +81,7 @@ dependencies { implementation 'com.google.firebase:firebase-analytics:17.2.2' implementation 'com.google.firebase:firebase-messaging:20.2.0' implementation 'androidx.work:work-runtime:2.3.4' + implementation "androidx.concurrent:concurrent-futures:1.0.0" testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 68d0b46..fb61996 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -72,7 +72,7 @@ - + diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/Autostart.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/Autostart.java deleted file mode 100644 index 739af96..0000000 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/Autostart.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.keyboardcrumbs.hassclient; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Build; -import android.os.IBinder; - -public class Autostart extends BroadcastReceiver { - - public void onReceive(Context context, Intent intent) { - if (Utils.requestingLocationUpdates(context) && Intent.ACTION_BOOT_COMPLETED.equalsIgnoreCase(intent.getAction())) { - Intent serviceIntent = new Intent(context, LocationUpdatesService.class); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(serviceIntent); - } else { - context.startService(serviceIntent); - } - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesAfterReboot.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesAfterReboot.java new file mode 100644 index 0000000..68029c1 --- /dev/null +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesAfterReboot.java @@ -0,0 +1,14 @@ +package com.keyboardcrumbs.hassclient; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class LocationUpdatesAfterReboot extends BroadcastReceiver { + + public void onReceive(Context context, Intent intent) { + if (LocationUtils.getLocationUpdatesState(context) == LocationUtils.LOCATION_UPDATES_SERVICE && Intent.ACTION_BOOT_COMPLETED.equalsIgnoreCase(intent.getAction())) { + LocationUtils.startServiceFromBroadcast(context); + } + } +} \ No newline at end of file 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 5c1a13b..728f436 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java @@ -7,7 +7,6 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.location.Location; -import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; @@ -78,9 +77,6 @@ public class LocationUpdatesService extends Service { }; mLocationRequest = new LocationRequest(); - mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY); - - getLastLocation(); HandlerThread handlerThread = new HandlerThread(TAG); handlerThread.start(); @@ -130,8 +126,10 @@ public class LocationUpdatesService extends Service { } private void requestLocationUpdates() { - long requestInterval = Utils.getLocationUpdateIntervals(getApplicationContext()); - Log.i(TAG, "Requesting location updates. Interval is " + requestInterval); + long requestInterval = LocationUtils.getLocationUpdateIntervals(getApplicationContext()); + int priority = LocationUtils.getLocationUpdatesPriority(getApplicationContext()); + Log.i(TAG, "Requesting location updates. Every " + requestInterval + "ms with priority of " + priority); + mLocationRequest.setPriority(priority); mLocationRequest.setInterval(requestInterval); mLocationRequest.setFastestInterval(requestInterval); startForeground(NOTIFICATION_ID, getNotification()); @@ -146,7 +144,7 @@ public class LocationUpdatesService extends Service { private Notification getNotification() { Intent intent = new Intent(this, LocationUpdatesService.class); - CharSequence text = Utils.getLocationText(mLocation); + CharSequence text = LocationUtils.getLocationText(mLocation); intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true); @@ -157,13 +155,13 @@ public class LocationUpdatesService extends Service { new Intent(this, MainActivity.class), 0); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) - .addAction(R.drawable.blank_icon, "Open HA Client", + .addAction(R.drawable.blank_icon, "Open app", activityPendingIntent) - .addAction(R.drawable.blank_icon, "Stop", + .addAction(R.drawable.blank_icon, "Stop tracking", servicePendingIntent) .setContentText(text) .setPriority(-1) - .setContentTitle(Utils.getLocationTitle(mLocation)) + .setContentTitle(LocationUtils.getLocationTitle(mLocation)) .setOngoing(true) .setSmallIcon(R.drawable.mini_icon) .setWhen(System.currentTimeMillis()); @@ -171,21 +169,6 @@ public class LocationUpdatesService extends Service { return builder.build(); } - private void getLastLocation() { - try { - mFusedLocationClient.getLastLocation() - .addOnCompleteListener(task -> { - if (task.isSuccessful() && task.getResult() != null) { - mLocation = task.getResult(); - } else { - Log.w(TAG, "Failed to get location."); - } - }); - } catch (SecurityException unlikely) { - Log.e(TAG, "Lost location permission." + unlikely); - } - } - private void onNewLocation(Location location) { Log.i(TAG, "New location: " + location); @@ -198,6 +181,7 @@ public class LocationUpdatesService extends Service { .build(); Data locationData = new Data.Builder() + .putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_LOCATION) .putDouble("Lat", mLocation.getLatitude()) .putDouble("Long", mLocation.getLongitude()) .putFloat("Acc", mLocation.getAccuracy()) @@ -205,7 +189,7 @@ public class LocationUpdatesService extends Service { OneTimeWorkRequest uploadWorkRequest = - new OneTimeWorkRequest.Builder(SendLocationWorker.class) + new OneTimeWorkRequest.Builder(SendDataHomeWorker.class) .setBackoffCriteria( BackoffPolicy.EXPONENTIAL, 10, diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesWorker.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesWorker.java new file mode 100644 index 0000000..5de1fdd --- /dev/null +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesWorker.java @@ -0,0 +1,100 @@ +package com.keyboardcrumbs.hassclient; + +import android.content.Context; +import android.location.Location; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.concurrent.futures.CallbackToFutureAdapter; +import androidx.work.BackoffPolicy; +import androidx.work.Constraints; +import androidx.work.Data; +import androidx.work.ExistingWorkPolicy; +import androidx.work.ListenableWorker; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; +import androidx.work.WorkerParameters; + +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationCallback; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.location.LocationResult; +import com.google.android.gms.location.LocationServices; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.concurrent.TimeUnit; + +public class LocationUpdatesWorker extends ListenableWorker { + + private Context currentContext; + private LocationCallback callback; + private FusedLocationProviderClient fusedLocationClient; + + public LocationUpdatesWorker(Context context, WorkerParameters params) { + super(context, params); + currentContext = context; + } + + private void finish() { + fusedLocationClient.removeLocationUpdates(callback); + } + + + + @NonNull + @Override + public ListenableFuture startWork() { + return CallbackToFutureAdapter.getFuture(completer -> { + fusedLocationClient = LocationServices.getFusedLocationProviderClient(currentContext); + + callback = new LocationCallback() { + @Override + public void onLocationResult(LocationResult locationResult) { + super.onLocationResult(locationResult); + Location location = locationResult.getLastLocation(); + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build(); + + Data locationData = new Data.Builder() + .putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_LOCATION) + .putDouble("Lat", location.getLatitude()) + .putDouble("Long", location.getLongitude()) + .putFloat("Acc", location.getAccuracy()) + .build(); + + + OneTimeWorkRequest uploadWorkRequest = + new OneTimeWorkRequest.Builder(SendDataHomeWorker.class) + .setBackoffCriteria( + BackoffPolicy.EXPONENTIAL, + 10, + TimeUnit.SECONDS) + .setConstraints(constraints) + .setInputData(locationData) + .build(); + + WorkManager + .getInstance(getApplicationContext()) + .enqueueUniqueWork("SendLocationUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest); + finish(); + completer.set(Result.success()); + } + }; + + LocationRequest locationRequest = new LocationRequest(); + int accuracy = LocationUtils.getLocationUpdatesPriority(getApplicationContext()); + locationRequest.setPriority(accuracy); + locationRequest.setInterval(5000); + locationRequest.setFastestInterval(1000); + try { + fusedLocationClient.requestLocationUpdates(locationRequest, + callback, Looper.myLooper()); + } catch (SecurityException e) { + completer.setException(e); + } + return callback; + }); + } +} diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUtils.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUtils.java new file mode 100644 index 0000000..af028a8 --- /dev/null +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUtils.java @@ -0,0 +1,78 @@ +package com.keyboardcrumbs.hassclient; + +import android.content.Context; +import android.content.Intent; +import android.location.Location; +import android.os.Build; + +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; + +import java.text.DateFormat; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +class LocationUtils { + + static final String KEY_REQUESTING_LOCATION_UPDATES = "flutter.location-updates-state"; + static final String KEY_LOCATION_UPDATE_INTERVAL = "flutter.location-updates-interval"; + static final String KEY_LOCATION_UPDATE_PRIORITY = "flutter.location-updates-priority"; + + static final String LOCATION_WORK_NAME = "HALocationWorker"; + + static final int LOCATION_UPDATES_DISABLED = 0; + static final int LOCATION_UPDATES_SERVICE = 1; + static final int LOCATION_UPDATES_WORKER = 2; + + static final int DEFAULT_LOCATION_UPDATE_INTERVAL_S = 900; //15 minutes + static final long MIN_WORKER_LOCATION_UPDATE_INTERVAL_MS = 900000; //15 minutes + + static int getLocationUpdatesState(Context context) { + return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getInt(KEY_REQUESTING_LOCATION_UPDATES, LOCATION_UPDATES_DISABLED); + } + + static long getLocationUpdateIntervals(Context context) { + return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getLong(KEY_LOCATION_UPDATE_INTERVAL, DEFAULT_LOCATION_UPDATE_INTERVAL_S) * 1000; + } + + static int getLocationUpdatesPriority(Context context) { + return (int) context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getLong(KEY_LOCATION_UPDATE_PRIORITY, 102); + } + + static void setLocationUpdatesState(Context context, int locationUpdatesState) { + context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) + .edit() + .putInt(KEY_REQUESTING_LOCATION_UPDATES, locationUpdatesState) + .apply(); + } + + static String getLocationText(Location location) { + return location == null ? "Accuracy: unknown" : + "Accuracy: " + location.getAccuracy(); + } + + static String getLocationTitle(Location location) { + return location == null ? "Requesting location..." : "Location updated at " + DateFormat.getDateTimeInstance().format(new Date(location.getTime())); + } + + static void startService(Context context) { + Intent myService = new Intent(context, LocationUpdatesService.class); + context.startService(myService); + } + + static void startServiceFromBroadcast(Context context) { + Intent serviceIntent = new Intent(context, LocationUpdatesService.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(serviceIntent); + } else { + context.startService(serviceIntent); + } + } + + static void startWorker(Context context, long interval) { + PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(LocationUpdatesWorker.class, interval, TimeUnit.MILLISECONDS) + .build(); + WorkManager.getInstance(context).enqueueUniquePeriodicWork(LocationUtils.LOCATION_WORK_NAME, ExistingPeriodicWorkPolicy.REPLACE, periodicWork); + } +} diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java index 2a3edfe..e106f7c 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java @@ -2,24 +2,19 @@ package com.keyboardcrumbs.hassclient; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.work.WorkManager; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugins.GeneratedPluginRegistrant; import android.Manifest; -import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.location.Location; import android.os.Bundle; -import android.os.IBinder; import io.flutter.plugin.common.MethodChannel; @@ -33,6 +28,9 @@ public class MainActivity extends FlutterActivity { private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; + private int locationUpdatesType = LocationUtils.LOCATION_UPDATES_DISABLED; + private long locationUpdatesInterval = LocationUtils.DEFAULT_LOCATION_UPDATE_INTERVAL_S * 1000; + @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); @@ -46,8 +44,7 @@ public class MainActivity extends FlutterActivity { .addOnCompleteListener(task -> { if (task.isSuccessful()) { String token = task.getResult().getToken(); - UpdateTokenTask updateTokenTask = new UpdateTokenTask(context); - updateTokenTask.execute(token); + context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).edit().putString("flutter.npush-token", token).apply(); result.success(token); } else { Exception ex = task.getException(); @@ -64,17 +61,25 @@ public class MainActivity extends FlutterActivity { } break; case "startLocationService": - Utils.setRequestingLocationUpdates(this, true); - if (isNoLocationPermissions()) { - requestLocationPermissions(); - } else { - startLocationService(); + try { + locationUpdatesInterval = LocationUtils.getLocationUpdateIntervals(this); + if (locationUpdatesInterval >= LocationUtils.MIN_WORKER_LOCATION_UPDATE_INTERVAL_MS) { + locationUpdatesType = LocationUtils.LOCATION_UPDATES_WORKER; + } else { + locationUpdatesType = LocationUtils.LOCATION_UPDATES_SERVICE; + } + if (isNoLocationPermissions()) { + requestLocationPermissions(); + } else { + startLocationUpdates(); + } + result.success(""); + } catch (Exception e) { + result.error("location_error", e.getMessage(), null); } - result.success(""); break; case "stopLocationService": - Utils.setRequestingLocationUpdates(this, false); - stopLocationService(); + stopLocationUpdates(); result.success(""); break; } @@ -86,24 +91,28 @@ public class MainActivity extends FlutterActivity { return (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS); } - private void startLocationService() { - Intent myService = new Intent(MainActivity.this, LocationUpdatesService.class); - startService(myService); + private void startLocationUpdates() { + if (locationUpdatesType == LocationUtils.LOCATION_UPDATES_SERVICE) { + LocationUtils.startService(this); + LocationUtils.setLocationUpdatesState(this, locationUpdatesType); + } else if (locationUpdatesType == LocationUtils.LOCATION_UPDATES_WORKER) { + LocationUtils.startWorker(this, locationUpdatesInterval); + LocationUtils.setLocationUpdatesState(this, locationUpdatesType); + } else { + stopLocationUpdates(); + } } - private void stopLocationService() { + private void stopLocationUpdates() { Intent myService = new Intent(MainActivity.this, LocationUpdatesService.class); stopService(myService); + WorkManager.getInstance(this).cancelUniqueWork(LocationUtils.LOCATION_WORK_NAME); + LocationUtils.setLocationUpdatesState(this, LocationUtils.LOCATION_UPDATES_DISABLED); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - /*if (Utils.requestingLocationUpdates(this)) { - if (isNoLocationPermissions()) { - requestLocationPermissions(); - } - }*/ } @Override @@ -142,7 +151,9 @@ public class MainActivity extends FlutterActivity { @NonNull int[] grantResults) { if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startLocationService(); + startLocationUpdates(); + } else { + stopLocationUpdates(); } } } diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java index b4376bb..525588d 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MessagingService.java @@ -3,7 +3,6 @@ package com.keyboardcrumbs.hassclient; import java.util.Map; import java.net.URL; import java.net.URLConnection; -import java.io.IOException; import java.io.InputStream; import android.app.NotificationChannel; @@ -14,6 +13,8 @@ import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; + +import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import com.google.firebase.messaging.FirebaseMessagingService; @@ -26,7 +27,7 @@ import android.webkit.URLUtil; public class MessagingService extends FirebaseMessagingService { - private static final String TAG = "MessagingService"; + public static final String NOTIFICATION_ACTION_BROADCAST = "com.keyboardcrumbs.hassclient.haNotificationAction"; @Override public void onMessageReceived(RemoteMessage remoteMessage) { @@ -39,19 +40,19 @@ public class MessagingService extends FirebaseMessagingService { } @Override - public void onNewToken(String token) { - UpdateTokenTask updateTokenTask = new UpdateTokenTask(this); - updateTokenTask.execute(token); + public void onNewToken(@NonNull String token) { + getApplicationContext().getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).edit().putString("flutter.npush-token", token).apply(); } private void sendNotification(Map data) { String channelId, messageBody, messageTitle, imageUrl, nTag, channelDescription; boolean autoCancel; - if (!data.containsKey("channelId")) { + String customChannelId = data.get("channelId"); + if (customChannelId == null) { channelId = "ha_notify"; channelDescription = "Default notification channel"; } else { - channelId = data.get("channelId"); + channelId = customChannelId; channelDescription = channelId; } if (!data.containsKey("body")) { @@ -114,7 +115,7 @@ public class MessagingService extends FirebaseMessagingService { } for (int i = 1; i <= 3; i++) { if (data.containsKey("action" + i)) { - Intent broadcastIntent = new Intent(this, NotificationActionReceiver.class); + Intent broadcastIntent = new Intent(this, NotificationActionReceiver.class).setAction(NOTIFICATION_ACTION_BROADCAST); if (autoCancel) { broadcastIntent.putExtra("tag", nTag); } diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NextAlarmBroadcastReceiver.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NextAlarmBroadcastReceiver.java index 36b4729..1deef0a 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NextAlarmBroadcastReceiver.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NextAlarmBroadcastReceiver.java @@ -7,19 +7,17 @@ import android.content.Intent; 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 androidx.work.WorkRequest; import java.util.concurrent.TimeUnit; public class NextAlarmBroadcastReceiver extends BroadcastReceiver { - private static final String TAG = "NextAlarmReceiver"; - @Override public void onReceive(Context context, Intent intent) { if (intent == null) { @@ -35,12 +33,17 @@ public class NextAlarmBroadcastReceiver extends BroadcastReceiver { .setRequiredNetworkType(NetworkType.CONNECTED) .build(); + Data workerData = new Data.Builder() + .putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_NEXT_ALARM) + .build(); + OneTimeWorkRequest uploadWorkRequest = - new OneTimeWorkRequest.Builder(UpdateNextAlarmWorker.class) + new OneTimeWorkRequest.Builder(SendDataHomeWorker.class) .setBackoffCriteria( BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS) + .setInputData(workerData) .setConstraints(constraints) .build(); diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java index 8c2e266..294e614 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/NotificationActionReceiver.java @@ -1,73 +1,58 @@ package com.keyboardcrumbs.hassclient; -import android.app.AlarmManager; import android.content.Context; -import android.util.Log; import android.content.BroadcastReceiver; import android.content.Intent; import android.app.NotificationManager; -import android.webkit.URLUtil; +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 org.json.JSONObject; -import android.content.SharedPreferences; +import java.util.concurrent.TimeUnit; public class NotificationActionReceiver extends BroadcastReceiver { - private static final String TAG = "NotificationAction"; - @Override public void onReceive(Context context, Intent intent) { if (intent == null) { return; } - + String intentAction = intent.getAction(); + if (intentAction == null || !intentAction.equalsIgnoreCase(MessagingService.NOTIFICATION_ACTION_BROADCAST)) { + return; + } String rawActionData = intent.getStringExtra("actionData"); if (intent.hasExtra("tag")) { String notificationTag = intent.getStringExtra("tag"); NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(notificationTag, 0); } - SharedPreferences prefs = context.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 actionData = new JSONObject(rawActionData); - if (URLUtil.isValidUrl(requestUrl)) { - JSONObject dataToSend = new JSONObject(); - JSONObject requestData = new JSONObject(); - if (actionData.getString("action").equals("call-service")) { - dataToSend.put("type", "call_service"); - requestData.put("domain", actionData.getString("service").split("\\.")[0]); - requestData.put("service", actionData.getString("service").split("\\.")[1]); - if (actionData.has("service_data")) { - requestData.put("service_data", actionData.get("service_data")); - } - } else { - dataToSend.put("type", "fire_event"); - requestData.put("event_type", "ha_client_event"); - JSONObject eventData = new JSONObject(); - eventData.put("action", actionData.getString("action")); - requestData.put("event_data", eventData); - } - dataToSend.put("data", requestData); - String stringRequest = dataToSend.toString(); - SendTask sendTask = new SendTask(); - sendTask.execute(requestUrl, stringRequest); - } else { - Log.w(TAG, "Invalid HA url"); - } - } catch (Exception e) { - Log.e(TAG, "Error handling notification action", e); - } - } else { - Log.w(TAG, "Webhook id not found"); - } + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build(); + Data workerData = new Data.Builder() + .putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_NOTIFICATION_ACTION) + .putString("rawActionData", rawActionData) + .build(); + + OneTimeWorkRequest uploadWorkRequest = + new OneTimeWorkRequest.Builder(SendDataHomeWorker.class) + .setBackoffCriteria( + BackoffPolicy.EXPONENTIAL, + 10, + TimeUnit.SECONDS) + .setInputData(workerData) + .setConstraints(constraints) + .build(); + + WorkManager + .getInstance(context) + .enqueueUniqueWork("NotificationAction", ExistingWorkPolicy.APPEND, uploadWorkRequest); } } \ No newline at end of file diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendDataHomeWorker.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendDataHomeWorker.java new file mode 100644 index 0000000..e9a5b1d --- /dev/null +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendDataHomeWorker.java @@ -0,0 +1,218 @@ +package com.keyboardcrumbs.hassclient; + +import android.app.AlarmManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.BatteryManager; +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.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +public class SendDataHomeWorker extends Worker { + public static final String DATA_TYPE_KEY = "dataType"; + + public static final int DATA_TYPE_LOCATION = 1; + public static final int DATA_TYPE_NEXT_ALARM = 2; + public static final int DATA_TYPE_NOTIFICATION_ACTION = 3; + + private Context currentContext; + private static final String TAG = "SendDataHomeWorker"; + + public static final String KEY_LAT_ARG = "Lat"; + public static final String KEY_LONG_ARG = "Long"; + public static final String KEY_ACC_ARG = "Acc"; + + private static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:00", Locale.ENGLISH); + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); + private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:00", Locale.ENGLISH); + + public SendDataHomeWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + currentContext = context; + } + + @NonNull + @Override + public Result doWork() { + Log.d(TAG, "Start sending data home"); + 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; + if (URLUtil.isValidUrl(requestUrl)) { + int dataType = getInputData().getInt(DATA_TYPE_KEY, 0); + String stringRequest; + if (dataType == DATA_TYPE_LOCATION) { + Log.d(TAG, "Location data"); + stringRequest = getLocationDataToSend(); + } else if (dataType == DATA_TYPE_NEXT_ALARM) { + Log.d(TAG, "Next alarm data"); + stringRequest = getNextAlarmDataToSend(); + } else if (dataType == DATA_TYPE_NOTIFICATION_ACTION) { + Log.d(TAG, "Notification action data"); + stringRequest = getNotificationActionData(); + } else { + Log.e(TAG, "doWork() unknown data type: " + dataType); + return Result.failure(); + } + try { + URL url = new URL(requestUrl); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setRequestMethod("POST"); + urlConnection.setRequestProperty("Content-Type", "application/json"); + urlConnection.setDoOutput(true); + assert stringRequest != null; + byte[] outputBytes = stringRequest.getBytes(StandardCharsets.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(); + } + + private String getLocationDataToSend() { + try { + JSONObject dataToSend = new JSONObject(); + 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)); + + BatteryManager bm; + if (android.os.Build.VERSION.SDK_INT >= 23) { + bm = currentContext.getSystemService(BatteryManager.class); + } else { + bm = (BatteryManager)currentContext.getSystemService(Context.BATTERY_SERVICE); + } + int batLevel = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + + dataObject.put("battery", batLevel); + + dataToSend.put("data", dataObject); + return dataToSend.toString(); + } catch (Exception e) { + Log.e(TAG,"getLocationDataToSend", e); + return null; + } + } + + private String getNotificationActionData() { + try { + String rawActionData = getInputData().getString("rawActionData"); + if (rawActionData == null || rawActionData.length() == 0) { + Log.e(TAG,"getNotificationActionData rawAction data is empty"); + return null; + } + JSONObject actionData = new JSONObject(rawActionData); + JSONObject dataToSend = new JSONObject(); + JSONObject requestData = new JSONObject(); + if (actionData.getString("action").equals("call-service")) { + dataToSend.put("type", "call_service"); + requestData.put("domain", actionData.getString("service").split("\\.")[0]); + requestData.put("service", actionData.getString("service").split("\\.")[1]); + if (actionData.has("service_data")) { + requestData.put("service_data", actionData.get("service_data")); + } + } else { + dataToSend.put("type", "fire_event"); + requestData.put("event_type", "ha_client_event"); + JSONObject eventData = new JSONObject(); + eventData.put("action", actionData.getString("action")); + requestData.put("event_data", eventData); + } + dataToSend.put("data", requestData); + return dataToSend.toString(); + } catch (Exception e) { + Log.e(TAG,"getNotificationActionData", e); + return null; + } + } + + private String getNextAlarmDataToSend() { + try { + final AlarmManager alarmManager; + if (android.os.Build.VERSION.SDK_INT >= 23) { + alarmManager = currentContext.getSystemService(AlarmManager.class); + } else { + alarmManager = (AlarmManager)currentContext.getSystemService(Context.ALARM_SERVICE); + } + + final AlarmManager.AlarmClockInfo alarmClockInfo = alarmManager.getNextAlarmClock(); + + JSONObject dataToSend = new JSONObject(); + dataToSend.put("type", "update_sensor_states"); + JSONArray dataArray = new JSONArray(); + JSONObject sensorData = new JSONObject(); + JSONObject sensorAttrs = new JSONObject(); + sensorData.put("unique_id", "next_alarm"); + sensorData.put("type", "sensor"); + final long triggerTimestamp; + if (alarmClockInfo != null) { + triggerTimestamp = alarmClockInfo.getTriggerTime(); + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(triggerTimestamp); + sensorData.put("state", DATE_TIME_FORMAT.format(calendar.getTime())); + sensorAttrs.put("date", DATE_FORMAT.format(calendar.getTime())); + sensorAttrs.put("time", TIME_FORMAT.format(calendar.getTime())); + sensorAttrs.put("timestamp", triggerTimestamp); + } else { + sensorData.put("state", ""); + sensorAttrs.put("date", ""); + sensorAttrs.put("time", ""); + sensorAttrs.put("timestamp", 0); + } + sensorData.put("icon", "mdi:alarm"); + sensorData.put("attributes", sensorAttrs); + dataArray.put(0, sensorData); + dataToSend.put("data", dataArray); + return dataToSend.toString(); + } catch (Exception e) { + Log.e(TAG,"getNextAlarmDataToSend", e); + return null; + } + } +} diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendLocationWorker.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendLocationWorker.java deleted file mode 100644 index 2cc79f1..0000000 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendLocationWorker.java +++ /dev/null @@ -1,98 +0,0 @@ -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(); - } -} diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendTask.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendTask.java deleted file mode 100644 index 76571cd..0000000 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/SendTask.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.keyboardcrumbs.hassclient; - -import android.util.Log; -import android.os.AsyncTask; - -import java.net.URL; -import java.net.HttpURLConnection; -import java.io.OutputStream; - -public class SendTask extends AsyncTask { - - private static final String TAG = "SendTask"; - - public SendTask(){ - //set context variables if required - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected String doInBackground(String... params) { - String urlString = params[0]; - String data = params[1]; - - try { - URL url = new URL(urlString); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - urlConnection.setRequestMethod("POST"); - urlConnection.setRequestProperty("Content-Type", "application/json"); - urlConnection.setDoOutput(true); - byte[] outputBytes = data.getBytes("UTF-8"); - OutputStream os = urlConnection.getOutputStream(); - os.write(outputBytes); - - int responseCode = urlConnection.getResponseCode(); - - urlConnection.disconnect(); - } catch (Exception e) { - Log.e(TAG, "Error sending data", e); - } - return null; - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateNextAlarmWorker.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateNextAlarmWorker.java deleted file mode 100644 index a6d69a6..0000000 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateNextAlarmWorker.java +++ /dev/null @@ -1,119 +0,0 @@ -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 UpdateNextAlarmWorker extends Worker { - - private Context currentContext; - private static final String TAG = "NextAlarmWorker"; - private static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:00", Locale.ENGLISH); - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); - private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:00", Locale.ENGLISH); - - public UpdateNextAlarmWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - currentContext = context; - } - - @NonNull - @Override - public Result doWork() { - final AlarmManager alarmManager; - if (android.os.Build.VERSION.SDK_INT >= 23) { - alarmManager = currentContext.getSystemService(AlarmManager.class); - } else { - alarmManager = (AlarmManager)currentContext.getSystemService(Context.ALARM_SERVICE); - } - - final AlarmManager.AlarmClockInfo alarmClockInfo = alarmManager.getNextAlarmClock(); - - 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_sensor_states"); - JSONArray dataArray = new JSONArray(); - JSONObject sensorData = new JSONObject(); - JSONObject sensorAttrs = new JSONObject(); - sensorData.put("unique_id", "next_alarm"); - sensorData.put("type", "sensor"); - final long triggerTimestamp; - if (alarmClockInfo != null) { - triggerTimestamp = alarmClockInfo.getTriggerTime(); - final Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(triggerTimestamp); - sensorData.put("state", DATE_TIME_FORMAT.format(calendar.getTime())); - sensorAttrs.put("date", DATE_FORMAT.format(calendar.getTime())); - sensorAttrs.put("time", TIME_FORMAT.format(calendar.getTime())); - sensorAttrs.put("timestamp", triggerTimestamp); - } else { - sensorData.put("state", ""); - sensorAttrs.put("date", ""); - sensorAttrs.put("time", ""); - sensorAttrs.put("timestamp", 0); - } - sensorData.put("icon", "mdi:alarm"); - sensorData.put("attributes", sensorAttrs); - dataArray.put(0, sensorData); - dataToSend.put("data", dataArray); - - 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 setting next alarm", e); - return Result.failure(); - } - } else { - Log.w(TAG, "Webhook id not found"); - return Result.failure(); - } - return Result.success(); - } -} diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java deleted file mode 100644 index 91455c0..0000000 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/UpdateTokenTask.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.keyboardcrumbs.hassclient; - -import android.util.Log; -import android.os.AsyncTask; - -import java.net.URL; -import java.net.HttpURLConnection; -import java.io.OutputStream; - -import android.webkit.URLUtil; - -import org.json.JSONObject; -import android.content.SharedPreferences; -import android.content.Context; -import java.lang.ref.WeakReference; - - -public class UpdateTokenTask extends AsyncTask { - - private static final String TAG = "UpdateTokenTask"; - - private WeakReference contextRef; - - public UpdateTokenTask(Context context){ - contextRef = new WeakReference<>(context); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected String doInBackground(String... params) { - Log.d(TAG, "Updating push token"); - Context context = contextRef.get(); - if (context != null) { - String token = params[0]; - SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString("flutter.npush-token", token); - editor.commit(); - } - return null; - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/Utils.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/Utils.java deleted file mode 100644 index 36de592..0000000 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/Utils.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.keyboardcrumbs.hassclient; - -import android.content.Context; -import android.location.Location; -import android.preference.PreferenceManager; - -import java.text.DateFormat; -import java.util.Date; - -class Utils { - - static final String KEY_REQUESTING_LOCATION_UPDATES = "flutter.foreground-location-service"; - static final String KEY_LOCATION_UPDATE_INTERVAL = "flutter.active-location-interval"; - - static boolean requestingLocationUpdates(Context context) { - return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getBoolean(KEY_REQUESTING_LOCATION_UPDATES, false); - } - - static long getLocationUpdateIntervals(Context context) { - return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getLong(KEY_LOCATION_UPDATE_INTERVAL, 90) * 1000; - } - - static void setRequestingLocationUpdates(Context context, boolean requestingLocationUpdates) { - context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) - .edit() - .putBoolean(KEY_REQUESTING_LOCATION_UPDATES, requestingLocationUpdates) - .apply(); - } - - static String getLocationText(Location location) { - return location == null ? "Accuracy: unknown" : - "Accuracy: " + location.getAccuracy(); - } - - static String getLocationTitle(Location location) { - return location == null ? "Requesting location..." : "Location updated at " + DateFormat.getDateTimeInstance().format(new Date(location.getTime())); - } -} diff --git a/lib/managers/app_settings.dart b/lib/managers/app_settings.dart index 009a03a..60ed778 100644 --- a/lib/managers/app_settings.dart +++ b/lib/managers/app_settings.dart @@ -32,7 +32,7 @@ class AppSettings { DisplayMode displayMode; AppTheme appTheme; final int defaultLocationUpdateIntervalMinutes = 20; - final int defaultActiveLocationUpdateIntervalSeconds = 90; + final int defaultActiveLocationUpdateIntervalSeconds = 900; Duration locationUpdateInterval; bool locationTrackingEnabled = false; diff --git a/lib/pages/settings/integration_settings.part.dart b/lib/pages/settings/integration_settings.part.dart index 52fc15e..f1e4b32 100644 --- a/lib/pages/settings/integration_settings.part.dart +++ b/lib/pages/settings/integration_settings.part.dart @@ -12,6 +12,12 @@ class IntegrationSettingsPage extends StatefulWidget { class _IntegrationSettingsPageState extends State { static const platform = const MethodChannel('com.keyboardcrumbs.hassclient/native'); + static final locationAccuracy = { + 100: "Highest", + 102: "Balanced (about 100 meters)", + 104: "Low (up to 10 kilometers)", + 105: "Passive (last known location)", + }; int _locationInterval = AppSettings().defaultLocationUpdateIntervalMinutes; int _activeLocationInterval = AppSettings().defaultActiveLocationUpdateIntervalSeconds; @@ -19,6 +25,7 @@ class _IntegrationSettingsPageState extends State { bool _foregroundLocationTrackingEnabled = false; bool _wait = false; bool _changedHere = false; + int _accuracy = 102; @override void initState() { @@ -33,10 +40,11 @@ class _IntegrationSettingsPageState extends State { SharedPreferences.getInstance().then((prefs) { setState(() { _locationTrackingEnabled = prefs.getBool("location-enabled") ?? false; - _foregroundLocationTrackingEnabled = prefs.getBool("foreground-location-service") ?? false; + _accuracy = prefs.getInt("location-updates-priority") ?? 102; + _foregroundLocationTrackingEnabled = (prefs.getInt("location-updates-state") ?? 0) > 0; _locationInterval = prefs.getInt("location-interval") ?? AppSettings().defaultLocationUpdateIntervalMinutes; - _activeLocationInterval = prefs.getInt("active-location-interval") ?? + _activeLocationInterval = prefs.getInt("location-updates-interval") ?? AppSettings().defaultActiveLocationUpdateIntervalSeconds; if (_locationInterval < 15) { _locationInterval = 15; @@ -94,9 +102,13 @@ class _IntegrationSettingsPageState extends State { } _switchForegroundLocationTrackingState(bool state) async { - await AppSettings().save({'active-location-interval': _activeLocationInterval}); + await AppSettings().save({'location-updates-interval': _activeLocationInterval, 'location-updates-priority': _accuracy}); if (state) { - await platform.invokeMethod('startLocationService'); + try { + await platform.invokeMethod('startLocationService'); + } catch (e) { + _foregroundLocationTrackingEnabled = false; + } } else { await platform.invokeMethod('stopLocationService'); } @@ -154,11 +166,7 @@ class _IntegrationSettingsPageState extends State { ), Container(height: Sizes.rowPadding), Text("Active location tracking", style: Theme.of(context).textTheme.title), - Text("Works in foreground noticeably affecting phone battery. Sends most accurate location getting it from phone if possible. Can be very frequent.", - style: Theme.of(context).textTheme.caption, - softWrap: true, - ), - Container(height: Sizes.rowPadding,), + Container(height: Sizes.rowPadding), Row( children: [ Text("Enable"), @@ -175,7 +183,26 @@ class _IntegrationSettingsPageState extends State { ], ), Container(height: Sizes.rowPadding), - Text("Update device location every"), + Text("Accuracy:", style: Theme.of(context).textTheme.body2), + Container(height: Sizes.rowPadding), + DropdownButton( + value: _accuracy, + iconSize: 30.0, + isExpanded: true, + items: locationAccuracy.keys.map((value) { + return new DropdownMenuItem( + value: value, + child: Text('${locationAccuracy[value]}'), + ); + }).toList(), + onChanged: _foregroundLocationTrackingEnabled ? null : (val) { + setState(() { + _accuracy = val; + }); + }, + ), + Container(height: Sizes.rowPadding), + Text("Update intervals"), Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max,