This commit is contained in:
estevez-dev 2020-07-06 22:41:42 +03:00
parent 7ebaf736b5
commit 2ec549af7e
5 changed files with 70 additions and 156 deletions

View File

@ -59,7 +59,7 @@
<service <service
android:name=".LocationUpdatesService" android:name=".LocationUpdatesService"
android:enabled="true" android:enabled="true"
android:exported="true" /> android:exported="false" />
<receiver android:name=".NotificationActionReceiver" android:exported="true"> <receiver android:name=".NotificationActionReceiver" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
@ -72,6 +72,11 @@
<action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" /> <action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".Autostart">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service <service
android:name="io.flutter.plugins.androidalarmmanager.AlarmService" android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"

View File

@ -0,0 +1,37 @@
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.IBinder;
public class Autostart extends BroadcastReceiver {
private PendingResult result;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LocationUpdatesService.LocalBinder binder = (LocationUpdatesService.LocalBinder) service;
binder.getService().requestLocationUpdates();
result.finish();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
public void onReceive(Context context, Intent intent) {
if (/*Utils.requestingLocationUpdates(context) && */Intent.ACTION_BOOT_COMPLETED.equalsIgnoreCase(intent.getAction())) {
context.getApplicationContext().bindService(new Intent(context, LocationUpdatesService.class), mServiceConnection,
Context.BIND_AUTO_CREATE);
result = goAsync();
}
}
}

View File

@ -1,14 +1,11 @@
package com.keyboardcrumbs.hassclient; package com.keyboardcrumbs.hassclient;
import android.app.ActivityManager;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration;
import android.location.Location; import android.location.Location;
import android.os.Binder; import android.os.Binder;
import android.os.Build; import android.os.Build;
@ -16,8 +13,9 @@ import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.work.BackoffPolicy; import androidx.work.BackoffPolicy;
import androidx.work.Constraints; import androidx.work.Constraints;
import androidx.work.Data; import androidx.work.Data;
@ -36,20 +34,6 @@ import com.google.android.gms.location.LocationServices;
import java.util.concurrent.TimeUnit; 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.
*
* For apps running in the background on "O" devices, location is computed only once every 10
* minutes and delivered batched every 30 minutes. This restriction applies even to apps
* targeting "N" or lower which are run on "O" devices.
*
* This sample show how to use a long-running service for location updates. When an activity is
* bound to this service, frequent location updates are permitted. When the activity is removed
* from the foreground, the service promotes itself to a foreground service, and location updates
* continue. When the activity comes back to the foreground, the foreground service stops, and the
* notification assocaited with that service is removed.
*/
public class LocationUpdatesService extends Service { public class LocationUpdatesService extends Service {
private static final String PACKAGE_NAME = private static final String PACKAGE_NAME =
@ -59,47 +43,23 @@ public class LocationUpdatesService extends Service {
private static final String CHANNEL_ID = "location_service"; private static final String CHANNEL_ID = "location_service";
static final String ACTION_BROADCAST = PACKAGE_NAME + ".broadcast";
static final String EXTRA_LOCATION = PACKAGE_NAME + ".location";
private static final String EXTRA_STARTED_FROM_NOTIFICATION = PACKAGE_NAME + private static final String EXTRA_STARTED_FROM_NOTIFICATION = PACKAGE_NAME +
".started_from_notification"; ".started_from_notification";
private final IBinder mBinder = new LocalBinder(); private final IBinder mBinder = new LocalBinder();
/**
* The identifier for the notification displayed for the foreground service.
*/
private static final int NOTIFICATION_ID = 954311; private static final int NOTIFICATION_ID = 954311;
/**
* Used to check whether the bound activity has really gone away and not unbound as part of an
* orientation change. We create a foreground service notification only if the former takes
* place.
*/
private boolean mChangingConfiguration = false;
private NotificationManager mNotificationManager; private NotificationManager mNotificationManager;
private LocationRequest mLocationRequest; private LocationRequest mLocationRequest;
private long requestInterval = 90000;
/**
* Provides access to the Fused Location Provider API.
*/
private FusedLocationProviderClient mFusedLocationClient; private FusedLocationProviderClient mFusedLocationClient;
/**
* Callback for changes in location.
*/
private LocationCallback mLocationCallback; private LocationCallback mLocationCallback;
private Handler mServiceHandler; private Handler mServiceHandler;
/**
* The current location.
*/
private Location mLocation; private Location mLocation;
public LocationUpdatesService() { public LocationUpdatesService() {
@ -117,7 +77,9 @@ public class LocationUpdatesService extends Service {
} }
}; };
createLocationRequest(); mLocationRequest = new LocationRequest();
mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
getLastLocation(); getLastLocation();
HandlerThread handlerThread = new HandlerThread(TAG); HandlerThread handlerThread = new HandlerThread(TAG);
@ -125,14 +87,11 @@ public class LocationUpdatesService extends Service {
mServiceHandler = new Handler(handlerThread.getLooper()); mServiceHandler = new Handler(handlerThread.getLooper());
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// Android O requires a Notification Channel.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Location updates"; CharSequence name = "Location updates";
// Create the channel for the notification
NotificationChannel mChannel = NotificationChannel mChannel =
new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW); new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW);
// Set the Notification Channel for the Notification Manager.
mNotificationManager.createNotificationChannel(mChannel); mNotificationManager.createNotificationChannel(mChannel);
} }
} }
@ -148,49 +107,24 @@ public class LocationUpdatesService extends Service {
removeLocationUpdates(); removeLocationUpdates();
stopSelf(); stopSelf();
} }
// Tells the system to not try to recreate the service after it has been killed.
return START_STICKY; return START_STICKY;
} }
@Override @Nullable
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mChangingConfiguration = true;
}
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
// Called when MainActivity comes to the foreground
// and binds with this service. The service should cease to be a foreground service
// when that happens.
Log.i(TAG, "in onBind()");
stopForeground(true);
mChangingConfiguration = false;
return mBinder; return mBinder;
} }
@Override @Override
public void onRebind(Intent intent) { public void onRebind(Intent intent) {
// Called when MainActivity returns to the foreground
// and binds once again with this service. The service should cease to be a foreground
// service when that happens.
Log.i(TAG, "in onRebind()");
stopForeground(true);
mChangingConfiguration = false;
super.onRebind(intent); super.onRebind(intent);
} }
@Override @Override
public boolean onUnbind(Intent intent) { public boolean onUnbind(Intent intent) {
Log.i(TAG, "Last client unbound from service"); return true;
// Called when the last client (MainActivity ) unbinds from this
// service. If this method is called due to a configuration change in MainActivity, we
// do nothing. Otherwise, we make this service a foreground service.
if (!mChangingConfiguration && Utils.requestingLocationUpdates(this)) {
startForeground(NOTIFICATION_ID, getNotification());
}
return true; // Ensures onRebind() is called when a client re-binds.
} }
@Override @Override
@ -199,16 +133,19 @@ public class LocationUpdatesService extends Service {
} }
public void requestLocationUpdates() { public void requestLocationUpdates() {
requestInterval = Utils.getLocationUpdateIntervals(getApplicationContext()); long requestInterval = Utils.getLocationUpdateIntervals(getApplicationContext());
Log.i(TAG, "Requesting location updates. Interval is " + requestInterval); Log.i(TAG, "Requesting location updates. Interval is " + requestInterval);
mLocationRequest.setInterval(requestInterval);
mLocationRequest.setFastestInterval(requestInterval);
Utils.setRequestingLocationUpdates(this, true); Utils.setRequestingLocationUpdates(this, true);
startService(new Intent(getApplicationContext(), LocationUpdatesService.class)); startService(new Intent(getApplicationContext(), LocationUpdatesService.class));
startForeground(NOTIFICATION_ID, getNotification());
try { try {
mFusedLocationClient.requestLocationUpdates(mLocationRequest, mFusedLocationClient.requestLocationUpdates(mLocationRequest,
mLocationCallback, Looper.myLooper()); mLocationCallback, Looper.myLooper());
} catch (SecurityException unlikely) { } catch (SecurityException unlikely) {
//When we lost permission //When we lost permission
Utils.setRequestingLocationUpdates(this, false); removeLocationUpdates();
} }
} }
@ -216,30 +153,24 @@ public class LocationUpdatesService extends Service {
Log.i(TAG, "Removing location updates"); Log.i(TAG, "Removing location updates");
try { try {
mFusedLocationClient.removeLocationUpdates(mLocationCallback); mFusedLocationClient.removeLocationUpdates(mLocationCallback);
Utils.setRequestingLocationUpdates(this, false);
stopSelf();
} catch (SecurityException unlikely) { } catch (SecurityException unlikely) {
//When we lost permission //When we lost permission
Utils.setRequestingLocationUpdates(this, true); Log.i(TAG, "No location permission");
} }
Utils.setRequestingLocationUpdates(this, false);
stopSelf();
} }
/**
* Returns the {@link NotificationCompat} used as part of the foreground service.
*/
private Notification getNotification() { private Notification getNotification() {
Intent intent = new Intent(this, LocationUpdatesService.class); Intent intent = new Intent(this, LocationUpdatesService.class);
CharSequence text = Utils.getLocationText(mLocation); CharSequence text = Utils.getLocationText(mLocation);
// Extra to help us figure out if we arrived in onStartCommand via the notification or not.
intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true); intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true);
// The PendingIntent that leads to a call to onStartCommand() in this service.
PendingIntent servicePendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent servicePendingIntent = PendingIntent.getService(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
// The PendingIntent to launch activity.
PendingIntent activityPendingIntent = PendingIntent.getActivity(this, 0, PendingIntent activityPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0); new Intent(this, MainActivity.class), 0);
@ -252,7 +183,7 @@ public class LocationUpdatesService extends Service {
.setPriority(-1) .setPriority(-1)
.setContentTitle(Utils.getLocationTitle(mLocation)) .setContentTitle(Utils.getLocationTitle(mLocation))
.setOngoing(true) .setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.drawable.mini_icon)
.setWhen(System.currentTimeMillis()); .setWhen(System.currentTimeMillis());
return builder.build(); return builder.build();
@ -278,21 +209,12 @@ public class LocationUpdatesService extends Service {
mLocation = location; mLocation = location;
// Notify anyone listening for broadcasts about the new location.
Intent intent = new Intent(ACTION_BROADCAST);
intent.putExtra(EXTRA_LOCATION, location);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
// Update notification content if running as a foreground service.
if (serviceIsRunningInForeground(this)) {
mNotificationManager.notify(NOTIFICATION_ID, getNotification()); mNotificationManager.notify(NOTIFICATION_ID, getNotification());
}
Constraints constraints = new Constraints.Builder() Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build(); .build();
// Create the Data object:
Data locationData = new Data.Builder() Data locationData = new Data.Builder()
.putDouble("Lat", mLocation.getLatitude()) .putDouble("Lat", mLocation.getLatitude())
.putDouble("Long", mLocation.getLongitude()) .putDouble("Long", mLocation.getLongitude())
@ -315,42 +237,9 @@ public class LocationUpdatesService extends Service {
.enqueueUniqueWork("SendLocationUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest); .enqueueUniqueWork("SendLocationUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest);
} }
/**
* Sets the location request parameters.
*/
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(requestInterval);
mLocationRequest.setFastestInterval(requestInterval);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
/**
* Class used for the client Binder. Since this service runs in the same process as its
* clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder { public class LocalBinder extends Binder {
LocationUpdatesService getService() { LocationUpdatesService getService() {
return LocationUpdatesService.this; return LocationUpdatesService.this;
} }
} }
/**
* Returns true if this is a foreground service.
*
* @param context The {@link Context}.
*/
public boolean serviceIsRunningInForeground(Context context) {
ActivityManager manager = (ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(
Integer.MAX_VALUE)) {
if (getClass().getName().equals(service.service.getClassName())) {
if (service.foreground) {
return true;
}
}
}
return false;
}
} }

View File

@ -20,7 +20,6 @@ import android.content.pm.PackageManager;
import android.location.Location; import android.location.Location;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.widget.Toast;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
@ -34,8 +33,6 @@ public class MainActivity extends FlutterActivity {
private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
private MyReceiver myReceiver;
private LocationUpdatesService mService = null; private LocationUpdatesService mService = null;
private boolean mBound = false; private boolean mBound = false;
@ -87,8 +84,8 @@ public class MainActivity extends FlutterActivity {
} }
break; break;
case "startLocationService": case "startLocationService":
if (checkPermissions()) { if (isNoLocationPermissions()) {
requestPermissions(); requestLocationPermissions();
} else { } else {
mService.requestLocationUpdates(); mService.requestLocationUpdates();
} }
@ -110,12 +107,11 @@ public class MainActivity extends FlutterActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
myReceiver = new MyReceiver(); /*if (Utils.requestingLocationUpdates(this)) {
if (Utils.requestingLocationUpdates(this)) { if (isNoLocationPermissions()) {
if (checkPermissions()) { requestLocationPermissions();
requestPermissions();
}
} }
}*/
} }
@Override @Override
@ -128,13 +124,10 @@ public class MainActivity extends FlutterActivity {
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(myReceiver,
new IntentFilter(LocationUpdatesService.ACTION_BROADCAST));
} }
@Override @Override
protected void onPause() { protected void onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver);
super.onPause(); super.onPause();
} }
@ -147,12 +140,12 @@ public class MainActivity extends FlutterActivity {
super.onStop(); super.onStop();
} }
private boolean checkPermissions() { private boolean isNoLocationPermissions() {
return PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, return PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION); Manifest.permission.ACCESS_FINE_LOCATION);
} }
private void requestPermissions() { private void requestLocationPermissions() {
ActivityCompat.requestPermissions(MainActivity.this, ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_PERMISSIONS_REQUEST_CODE); REQUEST_PERMISSIONS_REQUEST_CODE);
@ -168,14 +161,4 @@ public class MainActivity extends FlutterActivity {
} }
} }
private class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Location location = intent.getParcelableExtra(LocationUpdatesService.EXTRA_LOCATION);
if (location != null) {
//TODO looks like we need to remove this
}
}
}
} }

View File

@ -120,7 +120,7 @@ public class MessagingService extends FirebaseMessagingService {
} }
broadcastIntent.putExtra("actionData", data.get("action" + i + "_data")); broadcastIntent.putExtra("actionData", data.get("action" + i + "_data"));
PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT); PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT);
notificationBuilder.addAction(R.drawable.mini_icon, data.get("action" + i), actionIntent); notificationBuilder.addAction(R.drawable.blank_icon, data.get("action" + i), actionIntent);
} }
} }
NotificationManager notificationManager = NotificationManager notificationManager =