I've implemented some geofences into an app and have been testing them using mock locations on my test device (Samsung S4 running Android 5.0.1). While using mock locations, the transition detection has been working 100% of the time. I've now moved onto other Android devices and put the app on some phones which frequently (physically) enter and leave the geofence location, and I've noticed that the detection OFTEN doesn't work. This is really inconvenient so I was hoping there would be some way to make the detection more consistent.
My code which calls the geofence to be created:
private void startGeofenceMonitoring() {
Log.d(TAG, "startGeofenceMonitoring called");
try {
Geofence geofence = new Geofence.Builder()
.setRequestId(GEOFENCE_ID)
.setCircularRegion(51.364516, -0.189643, 150)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setNotificationResponsiveness(1000)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
.build();
GeofencingRequest geofencingRequest = new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER | GeofencingRequest.INITIAL_TRIGGER_EXIT)
.addGeofence(geofence)
.build();
Intent intent = new Intent(this, GeofenceService.class);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
if (!googleApiClient.isConnected()) {
Log.d(TAG, "GoogleApiClient is not connected");
} else {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MapsActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION);
return;
}
LocationServices.GeofencingApi.addGeofences(googleApiClient, geofencingRequest, pendingIntent)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.d(TAG, "Successfully added Geofence");
} else {
Log.d(TAG, "Failed to add geofence - " + status.getStatus());
}
}
});
}
} catch (SecurityException e) {
Log.d(TAG, "SecurityException - " + e.getMessage());
}
}
GeofenceService.java:
public class GeofenceService extends IntentService {
public static final String TAG = "GeofenceService";
DatabaseReference mRootRef = FirebaseDatabase.getInstance().getReference();
public GeofenceService() {
super(TAG);
}
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent event = GeofencingEvent.fromIntent(intent);
if (event.hasError()) {
//TODO:
} else {
double longitude = 0;
double latitude = 0;
int transition = event.getGeofenceTransition();
List<Geofence> geofences = event.getTriggeringGeofences();
Geofence geofence = geofences.get(0);
String requestID = geofence.getRequestId();
longitude = event.getTriggeringLocation().getLongitude();
latitude = event.getTriggeringLocation().getLatitude();
final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (longitude != 0 && latitude != 0) {
if (transition == Geofence.GEOFENCE_TRANSITION_ENTER) {
Log.d(TAG, "Entering geofence - " + requestID);
//My on enter code
} else if (transition == Geofence.GEOFENCE_TRANSITION_EXIT) {
Log.d(TAG, "Exited geofence - " + requestID);
//My on exit code
}
} else {
mRootRef.child("users/" + user.getUid() + "/error").setValue("lat/long = 0");
}
}
}
I've seen online that say using a broadcast receiver works better than what I've done but embarrassingly, I'm not quite good enough to really understand how to convert what I have done to what others have suggested.
Thanks.
check out this answer there is an implementation of broadcast receiver with geofence
Android Geofencing Notifications when app is closed
Related
So i've built a location-based reminder app using geofences.
Everything is working but one thing - the geofences are not triggering when the app is closed.
This kinda loses the entire idea of the app - sending notifications.
So i digged a bit and found out that if I open google maps, and press the GPS kinda thing to take the map to my location, the geofences trigger perfectly. But when I close it (or even put it in background), they won't trigger. I checked - and they get added like desired.
My question is - is this a known issue with the android studio emulator?
I'm using pixel 3 XL on API 33, if that is relevant.
Also, here's the broadcast receiver for my Geofences, if that is relevant too.
public class GeofenceBroadcastReceiver extends BroadcastReceiver
{
private static final String activityName = "GeofenceBroadcastReceiver";
#Override
public void onReceive(Context context, Intent intent)
{
Log.d(activityName, "Broadcast received: geofence triggered");
try
{
boolean isAppInForeground = new ForegroundCheckTask().execute(context).get();
if (isAppInForeground)
{
Log.d(activityName, "But app is not closed. Skip and do not send notification.");
return;
}
}
catch (Exception e)
{
e.printStackTrace();
}
NotificationHelper notificationHelper = new NotificationHelper(context);
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError())
{
Log.d(activityName, "onReceive: error");
return;
}
SharedPreferences sharedPref = context.getSharedPreferences("remindify_sp", MODE_PRIVATE);
String connectedUsername = sharedPref.getString("connected username", null);
List<Geofence> triggeredGeofences = geofencingEvent.getTriggeringGeofences();
ArrayList<LocationClass> locationsList = UtilityClass.getLocationListForUser(activityName, connectedUsername);
LocationClass notifiedLocation = findNotifiedLocationByGeofenceId(triggeredGeofences, locationsList);
int transitionType = geofencingEvent.getGeofenceTransition();
switch (transitionType)
{
case Geofence.GEOFENCE_TRANSITION_ENTER:
Toast.makeText(context, "Entered geofence", Toast.LENGTH_SHORT).show();
notificationHelper.sendHighPriorityNotification(
"Entered location: " + notifiedLocation.getName(),
"Click here to view it's task list!",
LoginActivity.class, notifiedLocation);
break;
case Geofence.GEOFENCE_TRANSITION_DWELL:
// Toast.makeText(context, "Dwelled geofence", Toast.LENGTH_SHORT).show();
break;
case Geofence.GEOFENCE_TRANSITION_EXIT:
// Toast.makeText(context, "Exited geofence", Toast.LENGTH_SHORT).show();
break;
}
}
private LocationClass findNotifiedLocationByGeofenceId(List<Geofence> triggeredGeofences,
ArrayList<LocationClass> locationsList)
{
Geofence geofence = triggeredGeofences.get(0);
String geofenceId = geofence.getRequestId();
for (LocationClass location : locationsList)
{
if (geofenceId.equals(String.valueOf(location.getId())))
{
// found location with corresponding geofence id
return location;
}
}
return null;
}
}
I'm new here. Anyways I've been scratching my head at this issue I have. I created a service that runs WiFiP2pHelper to discover nearby devices and retrieve WifiP2pDnsSdServiceInfo. I tested it on multiple phones, and they seem to work fine. If it works fine, it should return logs like these:
2020-08-12 13:32:23.449 24345-24345/com.example.acts D/WiFi P2P Helper: Service Request Added
2020-08-12 13:32:23.449 24345-24345/com.example.acts D/WiFi P2P Helper: Discover Services Successful
2020-08-12 13:32:23.473 24345-24345/com.example.acts D/WiFi P2P Helper: Local Service Added
Now I made the service to run in the background (even when the application is killed). It seems to work fine on the devices I've tested except any that runs on Android 10. I suspect it's something with Android 10 that causes WiFiP2pManager or WiFi P2P itself not to run in the background. But I'm not entirely sure if it's Android 10 causing it (I only got 2 Android 10 devices that were tested on). It returns the following logs:
2020-08-12 13:33:23.517 24345-24345/com.example.acts D/WiFi P2P Helper: Service Request Added
2020-08-12 13:33:23.517 24345-24345/com.example.acts D/WiFi P2P Helper: Error with P2P 0
2020-08-12 13:33:23.518 24345-24345/com.example.acts D/WiFi P2P Helper: Failed to Add Local Service
2020-08-12 13:33:23.518 24345-24345/com.example.acts E/WiFi P2P Helper: 0
As you can see, "Error with P2P 0" and "Failed to Add Local Service" points to the discoverServices and addLocalService. Below is the code. Sorry to dump it all here. Any help would really be appreciated! Thanks!
public class WiFiP2PHelper extends Service {
WifiP2pManager manager;
WifiP2pManager.Channel channel;
WifiP2pDnsSdServiceRequest serviceRequest;
IntentFilter intentFilter;
String iid;
private String baseServiceName = "ACTS";
private String serviceName = "";
final HashMap<String, String> buddies = new HashMap<String, String>();
final String TAG = "WiFi P2P Helper";
Handler handler = new Handler();
Runnable runnable;
int delay = 15000;
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onCreate() {
super.onCreate();
}
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
channel = manager.initialize(this, getMainLooper(), null);
intentFilter = new IntentFilter();
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
getUser();
startRegistration();
discoverService();
startMyOwnForeground();
return START_STICKY;
}
#RequiresApi(api = Build.VERSION_CODES.O)
private void startMyOwnForeground() {
String NOTIFICATION_CHANNEL_ID = "com.example.acts";
String channelName = "My Background Service";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("Hello World")
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(1333, notification);
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void getUser() {
List<User> users = User.getUser();
int count = users.size();
if (count > 0) {
User loggedInUser = users.get(0);
iid = loggedInUser.Id;
serviceName = baseServiceName + "_" + loggedInUser.Id;
Log.e("User", iid);
} else {
//uId = "User_Unregistered";
serviceName = baseServiceName + "_User_Unregistered";
Log.e("User in else", serviceName);
}
}
public String getWFDMacAddress() {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ntwInterface : interfaces) {
if (ntwInterface.getName().equalsIgnoreCase("p2p0")) {
byte[] byteMac = ntwInterface.getHardwareAddress();
if (byteMac == null) {
return null;
}
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < byteMac.length; i++) {
strBuilder.append(String.format("%02X:", byteMac[i]));
}
if (strBuilder.length() > 0) {
strBuilder.deleteCharAt(strBuilder.length() - 1);
}
return strBuilder.toString().toLowerCase();
}
}
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
return null;
}
public int findOpenSocket() throws IOException {
// Initialize a server socket on the next available port.
ServerSocket serverSocket = new ServerSocket(0);
// Store the chosen port.
int port = serverSocket.getLocalPort();
serverSocket.close();
return port;
}
private void startRegistration() {
manager.clearLocalServices(channel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.e(TAG, "onSuccess: Sucessss");
int port = 3030;
try {
port = findOpenSocket();
} catch (IOException e) {
e.printStackTrace();
}
Map record = new HashMap();
record.put("listenport", String.valueOf(port));
record.put("buddyname", iid);
record.put("available", "visible");
WifiP2pDnsSdServiceInfo serviceInfo =
WifiP2pDnsSdServiceInfo.newInstance(serviceName, "_presence._tcp", record);
manager.addLocalService(channel, serviceInfo, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.d(TAG, "Local Service Added");
}
#Override
public void onFailure(int arg0) {
Log.d(TAG, "Failed to Add Local Service");
Log.e(TAG, String.valueOf(arg0));
}
});
}
#Override
public void onFailure(int arg0) {
Log.d(TAG, "P2P Unsupported");
}
});
}
private void discoverService() {
WifiP2pManager.DnsSdTxtRecordListener txtListener = new WifiP2pManager.DnsSdTxtRecordListener() {
#Override
public void onDnsSdTxtRecordAvailable(
String fullDomain, Map record, WifiP2pDevice device) {
Log.d(TAG, "DnsSdTxtRecord available - " + record.toString());
buddies.put(device.deviceAddress, (String) record.get("buddyname"));
}
};
WifiP2pManager.DnsSdServiceResponseListener servListener = new WifiP2pManager.DnsSdServiceResponseListener() {
#Override
public void onDnsSdServiceAvailable(String instanceName, String registrationType,
WifiP2pDevice resourceType) {
resourceType.deviceName = buddies
.containsKey(resourceType.deviceAddress) ? buddies
.get(resourceType.deviceAddress) : resourceType.deviceName;
String uId = instanceName.replace(baseServiceName + "_", "");
Log.d(TAG, "Received " + uId);
/*Discovered device1 = new Discovered();
device1.discoveredthreeID = uId;
Discovered.insertTransactionToSQLite(device1);*/
}
};
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
manager.addServiceRequest(channel,
serviceRequest,
new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.d(TAG, "Service Request Added");
}
#Override
public void onFailure(int code) {
Log.d(TAG, "Failed to Add Service Request");
}
});
manager.setDnsSdResponseListeners(channel, servListener, txtListener);
manager.discoverServices(channel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.d(TAG, "Discover Services Successful");
}
#Override
public void onFailure(int code) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
if (code == WifiP2pManager.P2P_UNSUPPORTED) {
Log.d(TAG, "P2P isn't supported on this device.");
} else if (code == WifiP2pManager.ERROR) {
Log.d(TAG, "Error with P2P " + code);
} else if (code == WifiP2pManager.BUSY) {
Log.d(TAG, "P2P is Busy " + code);
}
}
});
}
}
Hi everyone who has the same issue as me. I found the solution to this. Basically, Android 10 (API 29) separates location permissions to the normal location permission and background location permission. You'll need to get your application to prompt for background permission (since it's not allowed by default).
Add these to your AndroidManifest.xml file
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
and in my MainActivity onCreate method I added a check to see if the device is on Android 10 (API 29) and if the permissions have already been granted
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) ||
(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 1);
}
} else {
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
}
If anyone else has a better way of implementing permission checks, please share it here. I'd like to learn as well. Thanks!
I'm trying to add geofencing in an android application but I keep getting and error at the last step of adding the list of geofences to the map. the application has the required permissions and the list is populated.
this is the code i have:
#RequiresApi(api = Build.VERSION_CODES.M)
public void addGeofences() {
Log.d(TAG, "addGeofences: ");
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions();
return;
}
mGeofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
.addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "onSuccess: geofenccess added!");
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "onFailure: failed to add geofences! : " + e.getMessage());
}
});
}
private GeofencingRequest getGeofencingRequest() {
Log.d(TAG, "getGeofencingRequest()");
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
// The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a
// GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device
// is already inside that geofence.
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
// Add the geofences to be monitored by geofencing service.
builder.addGeofences(mGeofenceList);
// Return a GeofencingRequest.
return builder.build();
}
private PendingIntent getGeofencePendingIntent() {
Log.d(TAG, "getGeofencePendingIntent: ");
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceBroadcastReceiver.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
mGeofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return mGeofencePendingIntent;
}
this is the error I get:
2019-11-03 16:52:10.183 21204-21204/com.alainsaris.thisisit D/MAP_ACTIVITY: addGeoFencing: created on:(50.642081,3.0992754), for: node/6638170780
2019-11-03 16:52:10.184 21204-21204/com.alainsaris.thisisit D/MAP_ACTIVITY: addGeoFencing: created on:(50.6521071,2.9742848), for: node/6659546245
2019-11-03 16:52:10.185 21204-21204/com.alainsaris.thisisit D/MAP_ACTIVITY: getGeofencingRequest()
2019-11-03 16:52:10.187 21204-21204/com.alainsaris.thisisit D/MAP_ACTIVITY: getGeofencePendingIntent:
2019-11-03 16:52:10.595 21204-21204/com.alainsaris.thisisit D/MAP_ACTIVITY: onFailure: failed to add geofences! : 13:
I can't find what does that error mean or what causes it. this is the part where the list is populated and geofences should be created after putting markers in the map.
#RequiresApi(api = Build.VERSION_CODES.M)
private void addGeoFencing() {
Log.d(TAG, "addGeoFencing: ");
JSONObject featuresObject = new JSONObject();
JSONObject stationObject = new JSONObject();
InputStream XmlFileInputStream = getResources().openRawResource(R.raw.lille_stations); // getting XML
String stationsObjectString = readTextFile(XmlFileInputStream);
try {
featuresObject = new JSONObject(stationsObjectString);
} catch (JSONException e) {
e.printStackTrace();
}
JSONArray featuresArray = null;
try {
featuresArray = new JSONArray(featuresObject.get("features").toString());
} catch (JSONException e) {
e.printStackTrace();
}
String stationName = "station name";
Double latitude = 0.0;
Double longitude = 0.0;
LatLng latLng;
for (int i = 0; i < featuresArray.length(); i++) {
try {
stationObject = (JSONObject) featuresArray.get(i);
} catch (JSONException e) {
e.printStackTrace();
}
try {
//get the station's name
stationName = (String) stationObject.getJSONObject("properties").get("name");
} catch (JSONException e) {
e.printStackTrace();
}
try {
//get the station's coordinates
JSONArray coordinatesArray;
coordinatesArray = new JSONArray(stationObject.getJSONObject("geometry").getJSONArray("coordinates").toString());
latitude = (Double) coordinatesArray.get(1);
longitude = (Double) coordinatesArray.get(0);
//add the marker
latLng = new LatLng(latitude, longitude);
mMap.addMarker(new MarkerOptions().position(latLng).title(stationName));
//add the geofence to the geofence list
mGeofenceList.add(new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
// .setRequestId(stationObject.getJSONObject("properties").getString("#id"))
.setRequestId("a test")
// Set the circular region of this geofence.
.setCircularRegion(
latitude,
longitude,
100)
// Set the expiration duration of the geofence. This geofence gets automatically
// removed after this period of time.
.setExpirationDuration(3000)
// Set the transition types of interest. Alerts are only generated for these
// transition. We track entry and exit transitions in this sample.
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
// Create the geofence.
.build());
Log.d(TAG, "addGeoFencing: created on:(" + latitude + "," + longitude + "), for: " + stationObject.getJSONObject("properties").getString("#id"));
} catch (JSONException e) {
e.printStackTrace();
}
}
//add the geofences to the geofencing client from the mGeofencesList
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions();
return;
}
mGeofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
.addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "onSuccess: geofenccess added!");
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "onFailure: failed to add geofences! : " + e.getMessage());
}
});
}
the app gets the names and the latlng from a geojson in R.raw
I am following the android guide android guide to build a simple native bridge for react-native for geofencing.
But I do not get any response when entering or leaving a geofence. It seems like the PendingIntent / IntentService for Transitions is not running properly.
MyModule looks basically like this. It also creates mGeofenceList like in the docs populated with data from react-native.
MyModule:
public class MyModule extends ReactContextBaseJavaModule {
//Build geofences
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeofenceList);
return builder.build();
}
//Build pending intent
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(reactContext, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
// calling addGeofences() and removeGeofences().
mGeofencePendingIntent = PendingIntent.getService(reactContext, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
return mGeofencePendingIntent;
}
#ReactMethod
public void startMonitoring() {
mGeofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.i(TAG, "Start Monitoring");
postNotification("Start Monitoring", "Pressed Start Monitoring");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "Start Monitoring: " + e.getMessage());
}
});
}
}
When running startMonitoring(), the notification (Start Monitoring) and the log gets produced, so I assume that the error is not in this part.
The IntentService looks also pretty basic /similar to the docs.
IntentService:
public class GeofenceTransitionsIntentService extends IntentService {
private static final String TAG = "GeofenceService";
private Handler handler;
SharedPreferences sp;
public GeofenceTransitionsIntentService(){
super(TAG);
}
#Override
public void onCreate() {
super.onCreate();
sp = PreferenceManager.getDefaultSharedPreferences(this);
handler = new Handler();
Log.i(TAG, "Intent created");
}
protected void onHandleIntent(Intent intent) {
Log.i(TAG, "onHandleIntent");
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = "Error Code: " + String.valueOf(geofencingEvent.getErrorCode());
Log.e(TAG, errorMessage);
return;
}
// Get the transition type.
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Get the transition details as a String.
String geofenceTransitionDetails = getGeofenceTransitionDetails(
geofenceTransition,
triggeringGeofences
);
// Send notification and log the transition details.
//sendNotification(geofenceTransitionDetails);
handler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), "Enter/Exit", Toast.LENGTH_SHORT).show();
}
});
Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
Log.e(TAG, "Invalid transition");
handler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), "ERROR", Toast.LENGTH_SHORT).show();
}
});
}
}
/*
Helpfunctions for logging
*/
private String getGeofenceTransitionDetails(
int geofenceTransition,
List<Geofence> triggeringGeofences) {
String geofenceTransitionString = getTransitionString(geofenceTransition);
// Get the Ids of each geofence that was triggered.
ArrayList<String> triggeringGeofencesIdsList = new ArrayList<>();
for (Geofence geofence : triggeringGeofences) {
triggeringGeofencesIdsList.add(geofence.getRequestId());
}
String triggeringGeofencesIdsString = TextUtils.join(", ", triggeringGeofencesIdsList);
return geofenceTransitionString + ": " + triggeringGeofencesIdsString;
}
private String getTransitionString(int transitionType) {
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
return "entered Geofence";
case Geofence.GEOFENCE_TRANSITION_EXIT:
return "exit Geofence";
default:
return "unknown Transition";
}
}
}
But none of the outputs of this class gets produced!
In the manifest of my native module I added the permission:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
And in the manifest of the TestApplication that uses this module I added this permission as well and in the application tag I added
<service android:name="com.mymodule.GeofenceTransitionsIntentService" android:exported="false"/>
I could not add this last line in the module's manifest, cause it was missing the application tag and has no activity. I am not sure if this is the right place.
I am testing in the emulator and change the location to a list of GPS data playback.
Questions
How can I verify that the ServiceIntent is running? Can I get Status of it?
Where does the logs appear? In com.TestApplication or somewhere else?
and of course:
3. Where is my error?
Ok, answering my own question, or specific only question 3:
The code above has no error, or at least and works as expected on a hardware device.
So, how to properly debug Geofencing on an emulator?
I have tried every suggestion I could find on the Internet to use Foreground Service to keep my app running beyond sleep and deep sleep modes but nothing has been successful so far.
I am working on a taxi booking application. I designed it to start emitting driver's location to server whenever the driver turns himself Online and stop emitting when Offline.
The following is the Foreground Service code that is started whenever driver turns himself Online and stopped when he presses online button which changes Common.CustomSocketOn to 0.
It work fine during screen wake and also works when the screen is off before the app gets killed.
However, even with WAKE_LOCK acquired, it still can't stay more than few minutes in sleep mode before getting killed by Android 7.
This failure to keep running in sleep mode breaks down many other features of the app because when the app gets killed silently, it does not get the chance to turn the driver Offline nor sign him out. As a result, the driver gets booking requests when his app is not running and therefore, cannot attend to it, and that keeps the booking from going to the next available driver. In fact, this causes so many other anomalies.
Please, can somebody tell me any other thing I need to do to keep Android from killing the Foreground Service.
public class OnlineForeGroundService extends Service {
private static final String TAG_FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
public static final String ACTION_START_FOREGROUND_SERVICE = "ACTION_START_FOREGROUND_SERVICE";
public static final String ACTION_STOP_FOREGROUND_SERVICE = "ACTION_STOP_FOREGROUND_SERVICE";
private static LocationListener locationListener;
private static LocationManager locationManager;
private PowerManager.WakeLock wakeLock;
public OnlineForeGroundService() {
}
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
//throw new UnsupportedOperationException("Not yet implemented");
return null;
}
#Override
public void onCreate() {
super.onCreate();
Log.d(TAG_FOREGROUND_SERVICE, "My foreground service.");
final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
try {
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "KEEP_AWAKE");
}
catch (Exception e){
e.printStackTrace();
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(intent != null)
{
String action = intent.getAction();
if(action != null) {
switch (action) {
case ACTION_START_FOREGROUND_SERVICE:
startForegroundService();
Toast.makeText(getApplicationContext(), getText(R.string.going_online), Toast.LENGTH_SHORT).show();
wakeLock.acquire();
break;
case ACTION_STOP_FOREGROUND_SERVICE:
stopForegroundService();
//Toast.makeText(getApplicationContext(), getText(R.string.going_offline), Toast.LENGTH_SHORT).show();
break;
}
}
}
return super.onStartCommand(intent, flags, startId);
//return START_STICKY;
}
/* Used to build and start foreground service. */
#SuppressLint("MissingPermission")
private void startForegroundService()
{
if(OnlineForeGroundService.locationManager == null) {
OnlineForeGroundService.locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
}
if(OnlineForeGroundService.locationListener == null) {
OnlineForeGroundService.locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
if (Common.CustomSocketOn == 1) {
SharedPreferences userPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if (Common.OldLatitude != 0 && Common.OldLongitude != 0) {
float distance[] = new float[1];
Location.distanceBetween(Common.OldLatitude, Common.OldLongitude, location.getLatitude(), location.getLongitude(), distance);
//Distance - 100
if (distance.length > 0 && distance[0] > 30) {
try {
JSONArray locAry = new JSONArray();
locAry.put(location.getLatitude());
locAry.put(location.getLongitude());
JSONObject emitobj = new JSONObject();
emitobj.put("coords", locAry);
emitobj.put("driver_name", userPref.getString("user_name", ""));
emitobj.put("driver_id", userPref.getString("id", ""));
emitobj.put("driver_status", "1"); //change by sir
emitobj.put("car_type", userPref.getString("car_type", ""));
emitobj.put("isdevice", "1");
emitobj.put("booking_status", userPref.getString("booking_status", ""));
emitobj.put("isLocationChange", 1);
if (location.getLatitude() != 0.0 && location.getLongitude() != 0.0 && Common.socket != null && Common.socket.connected()) {
Common.socket.emit("Create Driver Data", emitobj);
} else if (location.getLatitude() != 0.0 && location.getLongitude() != 0.0 && Common.socket == null) {
Common.socket = null;
SocketSingleObject.instance = null;
Common.socket = SocketSingleObject.get(getApplicationContext()).getSocket();
Common.socket.connect();
Common.socket.emit("Create Driver Data", emitobj);
} else if (location.getLatitude() != 0.0 && location.getLongitude() != 0.0 && Common.socket != null && !Common.socket.connected()) {
Common.socket.connect();
Common.socket.emit("Create Driver Data", emitobj);
}
} catch (JSONException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Common.OldLatitude = location.getLatitude();
Common.OldLongitude = location.getLongitude();
}
}
if (Common.OldLatitude == 0 && Common.OldLongitude == 0) {
Common.OldLatitude = location.getLatitude();
Common.OldLongitude = location.getLongitude();
}
}
else{
stopForegroundService();
}
}
#Override
public void onProviderDisabled(String provider) {
Log.d("Latitude", "disable");
}
#Override
public void onProviderEnabled(String provider) {
Log.d("Latitude", "enable");
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
}
if(Common.isPermission){
if(Common.CustomSocketOn == 1){
try {
OnlineForeGroundService.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, Common.DriverDistanceTime, Common.DriverDistance, OnlineForeGroundService.locationListener);
OnlineForeGroundService.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Common.DriverDistanceTime, Common.DriverDistance, OnlineForeGroundService.locationListener);
}
catch (Exception e){
e.printStackTrace();
}
}
}
Log.d(TAG_FOREGROUND_SERVICE, "Starting foreground service.");
String onlineSticker = getText(R.string.app_name)+" - Online";
Intent notificationIntent = new Intent(this, HomeActivity.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification;
/*if(android.os.Build.VERSION.SDK_INT >= 26) {
notification = new Notification.Builder(HomeActivity.class, NotificationManager.IMPORTANCE_HIGH)
.setContentTitle(getText(R.string.app_name))
.setContentText(getText(R.string.you_are_online))
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.setTicker(onlineSticker)
.build();
//startForeground(ONGOING_NOTIFICATION_ID, notification);
// Start foreground service.
}
else{*/
notification = new Notification.Builder(this)
.setContentTitle(getText(R.string.app_name))
.setContentText(getText(R.string.you_are_online))
.setPriority(Notification.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.setTicker(onlineSticker)
.build();
//}
startForeground(397, notification);
}
private void stopForegroundService()
{
Toast.makeText(getApplicationContext(), getText(R.string.going_offline), Toast.LENGTH_SHORT).show();
if(Common.isPermission){
if(Common.CustomSocketOn == 0){
try {
OnlineForeGroundService.locationManager.removeUpdates(OnlineForeGroundService.locationListener);
//OnlineForeGroundService.locationListener = null;
}
catch (Exception e){
e.printStackTrace();
}
}
}
Log.d(TAG_FOREGROUND_SERVICE, "Stop foreground service.");
if (null != wakeLock && wakeLock.isHeld()) {
wakeLock.release();
}
// Stop foreground service and remove the notification.
stopForeground(true);
// Stop the foreground service.
stopSelf();
}
}
Here is the androidManifest entry for the service and WAKE_LOCK permission:
<uses-permission android:name="android.permission.WAKE_LOCK" />
<service
android:name=".driver.service.OnlineForeGroundService"
android:process=".process"
android:enabled="true"
android:exported="true" ></service>
Please see this excellent site which provides a vast amount of information covering various different handset vendors and Android API versions.
This has helped me massively understand the very common Android developer problem of dealing with reports and feedback of foreground services being killed despite following the documented steps to ensure your service is setup correctly to avoid being shutdown.
https://dontkillmyapp.com/stock_android
The site includes mention of the Dianne Hackborn comments which are now no longer accessible on the Google plus (due to it's end of life).
Ultimately it seems that the solution to prevent your Foreground Service from being shutdown may vary across Android OS version and device manufacturer, but this site provides a good summary of the steps users can be directed to follow and also developers can implement (where possible) to try and mitigate this issue.
Check how you are starting your OnlineForeGroundService.
On Android Oreo and above it needs to be started with startForegroundService(Intent intent), not startService(Intent intent)
For example something like:
final Intent serviceIntent = new Intent(context, OnlineForeGroundService.class);
serviceIntent.setAction(OnlineForeGroundService.ACTION_START_FOREGROUND_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}