I'm doing a serious research on this topic for many days... I saw many topics here too...
But unfortunately I couldn't find a solution....
I'm writing an app that uses the new Google API for Geofence...
Well, I can handle "ins" and "outs" of a geofence, but only if my app is open! Even if I'm with wifi on, gps on, and 3G on, but the app, it does not trigger any event...Just if the app is open...
I'm using exactly the same GeofenceRequester class of the documentation http://developer.android.com/training/location/geofencing.html .
Even the class been the same I'll post the code here:
package br.com.marrs.imhere.geofence;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import br.com.marrs.imhere.services.ReceiveTransitionsIntentService;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener;
import com.google.android.gms.location.LocationStatusCodes;
/**
* Class for connecting to Location Services and requesting geofences.
* <b>
* Note: Clients must ensure that Google Play services is available before requesting geofences.
* </b> Use GooglePlayServicesUtil.isGooglePlayServicesAvailable() to check.
*
*
* To use a GeofenceRequester, instantiate it and call AddGeofence(). Everything else is done
* automatically.
*
*/
public class GeofenceRequester
implements
OnAddGeofencesResultListener,
ConnectionCallbacks,
OnConnectionFailedListener {
// Storage for a reference to the calling client
private final Activity mActivity;
// Stores the PendingIntent used to send geofence transitions back to the app
private PendingIntent mGeofencePendingIntent;
// Stores the current list of geofences
private ArrayList<Geofence> mCurrentGeofences;
// Stores the current instantiation of the location client
private LocationClient mLocationClient;
/*
* Flag that indicates whether an add or remove request is underway. Check this
* flag before attempting to start a new request.
*/
private boolean mInProgress;
public GeofenceRequester(Activity activityContext) {
// Save the context
mActivity = activityContext;
// Initialize the globals to null
mGeofencePendingIntent = null;
mLocationClient = null;
mInProgress = false;
}
/**
* Set the "in progress" flag from a caller. This allows callers to re-set a
* request that failed but was later fixed.
*
* #param flag Turn the in progress flag on or off.
*/
public void setInProgressFlag(boolean flag) {
// Set the "In Progress" flag.
mInProgress = flag;
}
/**
* Get the current in progress status.
*
* #return The current value of the in progress flag.
*/
public boolean getInProgressFlag() {
return mInProgress;
}
/**
* Returns the current PendingIntent to the caller.
*
* #return The PendingIntent used to create the current set of geofences
*/
public PendingIntent getRequestPendingIntent() {
return createRequestPendingIntent();
}
/**
* Start adding geofences. Save the geofences, then start adding them by requesting a
* connection
*
* #param geofences A List of one or more geofences to add
*/
public void addGeofences(List<Geofence> geofences) throws UnsupportedOperationException {
/*
* Save the geofences so that they can be sent to Location Services once the
* connection is available.
*/
mCurrentGeofences = (ArrayList<Geofence>) geofences;
// If a request is not already in progress
if (!mInProgress) {
// Toggle the flag and continue
mInProgress = true;
// Request a connection to Location Services
requestConnection();
// If a request is in progress
} else {
// Throw an exception and stop the request
throw new UnsupportedOperationException();
}
}
/**
* Request a connection to Location Services. This call returns immediately,
* but the request is not complete until onConnected() or onConnectionFailure() is called.
*/
private void requestConnection()
{
getLocationClient().connect();
}
/**
* Get the current location client, or create a new one if necessary.
*
* #return A LocationClient object
*/
private GooglePlayServicesClient getLocationClient()
{
if (mLocationClient == null) {
mLocationClient = new LocationClient(mActivity, this, this);
}
return mLocationClient;
}
/**
* Once the connection is available, send a request to add the Geofences
*/
private void continueAddGeofences() {
// Get a PendingIntent that Location Services issues when a geofence transition occurs
mGeofencePendingIntent = createRequestPendingIntent();
// Send a request to add the current geofences
mLocationClient.addGeofences(mCurrentGeofences, mGeofencePendingIntent, this);
}
/*
* Handle the result of adding the geofences
*/
#Override
public void onAddGeofencesResult(int statusCode, String[] geofenceRequestIds)
{
// Create a broadcast Intent that notifies other components of success or failure
Intent broadcastIntent = new Intent();
// Temp storage for messages
String msg;
// If adding the geocodes was successful
if (LocationStatusCodes.SUCCESS == statusCode)
{
// Create a message containing all the geofence IDs added.
msg = geofenceRequestIds.toString();
// In debug mode, log the result
Log.d("DEBUG", msg);
// Create an Intent to broadcast to the app
broadcastIntent.setAction("br.com.marrs.imhere.ACTION_GEOFENCES_ADDED")
.addCategory("br.com.marrs.imhere.CATEGORY_LOCATION_SERVICES")
.putExtra("br.com.marrs.imhere.EXTRA_GEOFENCE_STATUS", msg);
// If adding the geofences failed
}
else
{
/*
* Create a message containing the error code and the list
* of geofence IDs you tried to add
*/
msg = "Erro adicionando geofence";
// Log an error
Log.e("DEBUG", msg);
// Create an Intent to broadcast to the app
broadcastIntent.setAction("br.com.marrs.imhere.ACTION_GEOFENCE_ERROR")
.addCategory("br.com.marrs.imhere.CATEGORY_LOCATION_SERVICES")
.putExtra("br.com.marrs.imhere.EXTRA_GEOFENCE_STATUS", msg);
}
// Broadcast whichever result occurred
LocalBroadcastManager.getInstance(mActivity).sendBroadcast(broadcastIntent);
// Disconnect the location client
requestDisconnection();
}
/**
* Get a location client and disconnect from Location Services
*/
private void requestDisconnection() {
// A request is no longer in progress
mInProgress = false;
getLocationClient().disconnect();
}
/*
* Called by Location Services once the location client is connected.
*
* Continue by adding the requested geofences.
*/
#Override
public void onConnected(Bundle arg0) {
// If debugging, log the connection
Log.d("DEBUG", "GeofenceRequester connected");
// Continue adding the geofences
continueAddGeofences();
}
/*
* Called by Location Services once the location client is disconnected.
*/
#Override
public void onDisconnected() {
// Turn off the request flag
mInProgress = false;
// In debug mode, log the disconnection
Log.d("DEBUG", "GeofenceRequester disconnected");
// Destroy the current location client
mLocationClient = null;
}
/**
* Get a PendingIntent to send with the request to add Geofences. Location Services issues
* the Intent inside this PendingIntent whenever a geofence transition occurs for the current
* list of geofences.
*
* #return A PendingIntent for the IntentService that handles geofence transitions.
*/
private PendingIntent createRequestPendingIntent() {
// If the PendingIntent already exists
if (null != mGeofencePendingIntent) {
// Return the existing intent
return mGeofencePendingIntent;
// If no PendingIntent exists
} else {
// Create an Intent pointing to the IntentService
Intent intent = new Intent(mActivity, ReceiveTransitionsIntentService.class);
/*
* Return a PendingIntent to start the IntentService.
* Always create a PendingIntent sent to Location Services
* with FLAG_UPDATE_CURRENT, so that sending the PendingIntent
* again updates the original. Otherwise, Location Services
* can't match the PendingIntent to requests made with it.
*/
return PendingIntent.getService(
mActivity,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}
/*
* Implementation of OnConnectionFailedListener.onConnectionFailed
* If a connection or disconnection request fails, report the error
* connectionResult is passed in from Location Services
*/
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// Turn off the request flag
mInProgress = false;
/*
* Google Play services can resolve some errors it detects.
* If the error has a resolution, try sending an Intent to
* start a Google Play services activity that can resolve
* error.
*/
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(mActivity, 9000);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
} catch (SendIntentException e) {
// Log the error
e.printStackTrace();
}
/*
* If no resolution is available, put the error code in
* an error Intent and broadcast it back to the main Activity.
* The Activity then displays an error dialog.
* is out of date.
*/
} else {
Intent errorBroadcastIntent = new Intent("br.com.marrs.imhere.ACTION_CONNECTION_ERROR");
errorBroadcastIntent.addCategory("br.com.marrs.imhere.CATEGORY_LOCATION_SERVICES")
.putExtra("br.com.marrs.imhere.EXTRA_CONNECTION_ERROR_CODE",
connectionResult.getErrorCode());
LocalBroadcastManager.getInstance(mActivity).sendBroadcast(errorBroadcastIntent);
}
}
}
And the service:
package br.com.marrs.imhere.services;
import br.com.marrs.imhere.ImHereActivity;
import br.com.marrs.imhere.R;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.LocationClient;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import java.util.List;
/**
* This class receives geofence transition events from Location Services, in the
* form of an Intent containing the transition type and geofence id(s) that triggered
* the event.
*/
public class ReceiveTransitionsIntentService extends IntentService {
/**
* Sets an identifier for this class' background thread
*/
public ReceiveTransitionsIntentService()
{
super("ReceiveTransitionsIntentService");
}
/**
* Handles incoming intents
* #param intent The Intent sent by Location Services. This Intent is provided
* to Location Services (inside a PendingIntent) when you call addGeofences()
*/
#Override
protected void onHandleIntent(Intent intent) {
// Create a local broadcast Intent
Intent broadcastIntent = new Intent();
// Give it the category for all intents sent by the Intent Service
broadcastIntent.addCategory("br.com.marrs.imhere.CATEGORY_LOCATION_SERVICES");
// First check for errors
if (LocationClient.hasError(intent)) {
// Get the error code
int errorCode = LocationClient.getErrorCode(intent);
// Log the error
Log.e("DEBUG", "Erro no service LocationClient has error");
// Set the action and error message for the broadcast intent
broadcastIntent.setAction("br.com.marrs.imhere.ACTION_GEOFENCES_ERROR").putExtra("br.com.marrs.imhere.EXTRA_GEOFENCE_STATUS", "problemas");
// Broadcast the error *locally* to other components in this app
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
// If there's no error, get the transition type and create a notification
} else {
// Get the type of transition (entry or exit)
int transition = LocationClient.getGeofenceTransition(intent);
// Test that a valid transition was reported
if (
(transition == Geofence.GEOFENCE_TRANSITION_ENTER)
||
(transition == Geofence.GEOFENCE_TRANSITION_EXIT)
) {
// Post a notification
List<Geofence> geofences = LocationClient.getTriggeringGeofences(intent);
String[] geofenceIds = new String[geofences.size()];
for (int index = 0; index < geofences.size() ; index++) {
geofenceIds[index] = geofences.get(index).getRequestId();
}
String ids = TextUtils.join(",",geofenceIds);
String transitionType = getTransitionString(transition);
sendNotification(transitionType, ids);
// Log the transition type and a message
Log.d("DEBUG","Ae...n sei pq isso....mas parece que tah ok");
// An invalid transition was reported
} else {
// Always log as an error
Log.e("DEBUG","Erro, erro, erro");
}
}
}
/**
* Posts a notification in the notification bar when a transition is detected.
* If the user clicks the notification, control goes to the main Activity.
* #param transitionType The type of transition that occurred.
*
*/
private void sendNotification(String transitionType, String ids)
{
// Create an explicit content Intent that starts the main Activity
Intent notificationIntent =
new Intent(getApplicationContext(),ImHereActivity.class);
// Construct a task stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Adds the main Activity to the task stack as the parent
stackBuilder.addParentStack(ImHereActivity.class);
// Push the content Intent onto the stack
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Set the notification contents
builder.setSmallIcon(R.drawable.abs__ic_clear)
.setContentTitle(ids)
.setContentText(transitionType)
.setContentIntent(notificationPendingIntent);
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
/**
* Maps geofence transition types to their human-readable equivalents.
* #param transitionType A transition type constant defined in Geofence
* #return A String indicating the type of transition
*/
private String getTransitionString(int transitionType) {
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
return "Entrando";
case Geofence.GEOFENCE_TRANSITION_EXIT:
return "Saindo";
default:
return "Desconhecido";
}
}
}
And the Broadcast receiver in the Activity:
public class GeofenceSampleReceiver extends BroadcastReceiver
{
/*
* Define the required method for broadcast receivers
* This method is invoked when a broadcast Intent triggers the receiver
*/
#Override
public void onReceive(Context context, Intent intent)
{
// Check the action code and determine what to do
String action = intent.getAction();
// Intent contains information about errors in adding or removing geofences
if (TextUtils.equals(action, "br.com.marrs.imhere.ACTION_GEOFENCE_ERROR"))
{
handleGeofenceError(context, intent);
// Intent contains information about successful addition or removal of geofences
}
else if (
TextUtils.equals(action, "br.com.marrs.imhere.ACTION_GEOFENCES_ADDED")
||
TextUtils.equals(action, "br.com.marrs.imhere.ACTION_GEOFENCES_REMOVED"))
{
handleGeofenceStatus(context, intent);
// Intent contains information about a geofence transition
} else if (TextUtils.equals(action, "br.com.marrs.imhere.ACTION_GEOFENCE_TRANSITION"))
{
handleGeofenceTransition(context, intent);
// The Intent contained an invalid action
}
else
{
Log.e("DEBUG", "Invalid action detail");
Toast.makeText(context, "Invalid action detail", Toast.LENGTH_LONG).show();
}
}
/**
* If you want to display a UI message about adding or removing geofences, put it here.
*
* #param context A Context for this component
* #param intent The received broadcast Intent
*/
private void handleGeofenceStatus(Context context, Intent intent) {
}
/**
* Report geofence transitions to the UI
*
* #param context A Context for this component
* #param intent The Intent containing the transition
*/
private void handleGeofenceTransition(Context context, Intent intent) {
/*
* If you want to change the UI when a transition occurs, put the code
* here. The current design of the app uses a notification to inform the
* user that a transition has occurred.
*/
}
/**
* Report addition or removal errors to the UI, using a Toast
*
* #param intent A broadcast Intent sent by ReceiveTransitionsIntentService
*/
private void handleGeofenceError(Context context, Intent intent)
{
String msg = intent.getStringExtra("br.com.marrs.imhere.EXTRA_GEOFENCE_STATUS");
Log.e("DEBUG", msg);
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
}
}
And here is the piece of code that I use to create a GEofence before send to GeofenceRequester.
int raio = Integer.parseInt(spinner.getAdapter().getItem(spinner.getSelectedItemPosition()).toString());
int transitionType = (in.isChecked())?Geofence.GEOFENCE_TRANSITION_ENTER:Geofence.GEOFENCE_TRANSITION_EXIT;
Geofence geofence = new Geofence.Builder().setRequestId(nomeGeofence.getText().toString()).setTransitionTypes(transitionType).setCircularRegion(lat, lon, raio).setExpirationDuration(Geofence.NEVER_EXPIRE).build();
geofences.add(geofence);
try
{
mGeofenceRequester.addGeofences(geofences);
addCircleGeofence(raio);
}
catch (UnsupportedOperationException e)
{
Toast.makeText(getActivity(), "Já existe uma requisição de add em andamento",Toast.LENGTH_LONG).show();
}
Any help will be great!
Thanks!
I had the same exact problem. Here is what I answered over there: So after playing around with this a bit, it looks like the ReceiveTransitionsIntentService (as defined in the sample code) will stop getting the notifications when the app is not around. I think this is a big problem with the example code... Seems like that will trip folks like me up.
So I used a broadcast receiver instead, and so far it seems to be working from my tests.
Add this to the manifest:
<receiver android:name="com.aol.android.geofence.GeofenceReceiver"
android:exported="false">
<intent-filter >
<action android:name="com.aol.android.geofence.ACTION_RECEIVE_GEOFENCE"/>
</intent-filter>
</receiver>
Then in the GeofenceRequester class you need to change the createRequestPendingIntent method so that it goes to your BroadcastReceiver instead of the ReceiveTransitionsIntentService. MAKE SURE AND NOTE the change to .getBroadcast instead of getService. That got me hung up for a while.
private PendingIntent createRequestPendingIntent() {
// If the PendingIntent already exists
if (null != mGeofencePendingIntent) {
// Return the existing intent
return mGeofencePendingIntent;
// If no PendingIntent exists
} else {
// Create an Intent pointing to the IntentService
Intent intent = new Intent("com.aol.android.geofence.ACTION_RECEIVE_GEOFENCE");
//MAKE SURE YOU CHANGE THIS TO getBroadcast if you are coming from the sample code.
return PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}
Then I added the GeofenceReceiver class that looks something like this:
public class GeofenceReceiver extends BroadcastReceiver {
Context context;
Intent broadcastIntent = new Intent();
#Override
public void onReceive(Context context, Intent intent) {
this.context = context;
broadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES);
if (LocationClient.hasError(intent)) {
handleError(intent);
} else {
handleEnterExit(intent);
}
}
private void handleError(Intent intent){
// Get the error code
int errorCode = LocationClient.getErrorCode(intent);
// Get the error message
String errorMessage = LocationServiceErrorMessages.getErrorString(
context, errorCode);
// Log the error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_error_detail,
errorMessage));
// Set the action and error message for the broadcast intent
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, errorMessage);
// Broadcast the error *locally* to other components in this app
LocalBroadcastManager.getInstance(context).sendBroadcast(
broadcastIntent);
}
private void handleEnterExit(Intent intent) {
// Get the type of transition (entry or exit)
int transition = LocationClient.getGeofenceTransition(intent);
// Test that a valid transition was reported
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)
|| (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
// Post a notification
List<Geofence> geofences = LocationClient
.getTriggeringGeofences(intent);
String[] geofenceIds = new String[geofences.size()];
String ids = TextUtils.join(GeofenceUtils.GEOFENCE_ID_DELIMITER,
geofenceIds);
String transitionType = GeofenceUtils
.getTransitionString(transition);
for (int index = 0; index < geofences.size(); index++) {
Geofence geofence = geofences.get(index);
...do something with the geofence entry or exit. I'm saving them to a local sqlite db
}
// Create an Intent to broadcast to the app
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_TRANSITION)
.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_ID, geofenceIds)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_TRANSITION_TYPE,
transitionType);
LocalBroadcastManager.getInstance(MyApplication.getContext())
.sendBroadcast(broadcastIntent);
// Log the transition type and a message
Log.d(GeofenceUtils.APPTAG, transitionType + ": " + ids);
Log.d(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_notification_text));
// In debug mode, log the result
Log.d(GeofenceUtils.APPTAG, "transition");
// An invalid transition was reported
} else {
// Always log as an error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_invalid_type,
transition));
}
}
/**
* Posts a notification in the notification bar when a transition is
* detected. If the user clicks the notification, control goes to the main
* Activity.
*
* #param transitionType
* The type of transition that occurred.
*
*/
private void sendNotification(String transitionType, String locationName) {
// Create an explicit content Intent that starts the main Activity
Intent notificationIntent = new Intent(context, MainActivity.class);
// Construct a task stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the main Activity to the task stack as the parent
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack
PendingIntent notificationPendingIntent = stackBuilder
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions
// >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(
context);
// Set the notification contents
builder.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(transitionType + ": " + locationName)
.setContentText(
context.getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Get an instance of the Notification manager
NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
}
Hopefully that helps someone else.
I had a similar issue and after trying various ways I could finally fix the issue. Unfortunately, the android documentation fails to mention these issues.
The android geofences get removed every time you reboot your device or every time you toggle the location mode.
So Add a broadcast receiver to listen to device reboots and location mode changes, and add the geofences again in the receiver.
I have posted a detailed explanation here
Related
I'm having trouble sending a string extra with my PendingIntent that I pass to LocationServices.FusedLocationApi.requestLocationUpdates(GoogleApiClient client, LocationRequest request, PendingIntent callbackIntent).
It appears that the username extra i'm putting onto the Intent is mangling the location that requestLocationUpdates is trying to hand off to my IntentService as intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED) returns null.
EDIT
I've tried making a User class that implements Parcelable and putting it as an extra:
mRequestLocationUpdatesIntent.putExtra("username", new User(username));
and I've also tried to put the Parcelable User inside a Bundle as suggested via comment in this bug report https://code.google.com/p/android/issues/detail?id=81812:
Bundle userBundle = new Bundle();
userBundle.putParcelable("user", new User(username));
mRequestLocationUpdatesIntent.putExtra("user", userBundle);
in my service:
Bundle userBundle = intent.getBundleExtra("user");
User user = userBundle.getParcelable("user");
String username = user.getUsername();
However neither of these approaches has made any difference. Whenever I put any extra onto my intent, the location is never added to the intent when the updates occur.
I setup this IntentService to handle location updates:
public class LocationUpdateService extends IntentService {
private final String TAG = "LocationUpdateService";
public LocationUpdateService() {
super("LocationUpdateService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent");
Bundle extras = intent.getExtras();
Log.d(TAG, "keys found inside intent: " + TextUtils.join(", ", extras.keySet()));
String username = intent.getStringExtra("username");
if (username != null) {
Log.d(TAG, "username: " + username);
} else {
Log.d(TAG, "username: null");
}
if (!intent.hasExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED)) {
Log.d(TAG, "intent does not have location :(");
}
Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
if (location == null) {
Log.d(TAG, "location == null :(");
}
Log.d(TAG, "latitude " + String.valueOf(location.getLatitude()));
Log.d(TAG, "longitude " + String.valueOf(location.getLongitude()));
...
}
}
When the user clicks a button, the startLocationUpdates is called in my main activity:
main activity class:
...
Boolean mLocationUpdatesEnabled = false;
protected void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(LOCATION_UPDATE_INTERVAL);
mLocationRequest.setFastestInterval(LOCATION_UPDATE_FASTEST_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
protected void startLocationUpdates() {
Log.d(TAG, "startng location updates...");
mLocationUpdatesEnabled = true;
if (mLocationRequest == null) {
createLocationRequest();
}
// create the Intent to use WebViewActivity to handle results
Intent mRequestLocationUpdatesIntent = new Intent(this, LocationUpdateService.class);
// create a PendingIntent
mRequestLocationUpdatesPendingIntent = PendingIntent.getService(getApplicationContext(), 0,
mRequestLocationUpdatesIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
// request location updates
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
mLocationRequest,
mRequestLocationUpdatesPendingIntent);
Log.d(TAG, "location updates started");
}
protected void stopLocationUpdates() {
Log.d(TAG, "stopping location updates...");
mLocationUpdatesEnabled = false;
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient,
mRequestLocationUpdatesPendingIntent);
Log.d(TAG, "location updates stopped");
}
This all works well and good; When the user presses the button, toggleLocationUpdates is called, which calls LocationServices.FusedLocationApi.requestLocationUpdates which calls my LocationUpdateService where I'm able to get the location.
The trouble comes when I tried to put a string extra onto my Intent using Intent.putExtra(String, String):
main activity class:
...
protected void startLocationUpdates(String username) {
....
// create the Intent to use WebViewActivity to handle results
Intent mRequestLocationUpdatesIntent = new Intent(this, LocationUpdateService.class);
//////////////////////////////////////////////////////////////////
//
// When I put this extra, IntentService sees my username extra
// but the parcelableExtra `location` == null :(
//
//////////////////////////////////////////////////////////////////
mRequestLocationUpdatesIntent.putExtra("username", username);
...
}
...
EDIT I had started the next sentence as a statement rather than a question: "I am using..."
Am I using the correct approach to sending some extra data to this location update handling IntentService or is there a more-sane way to go about this?
Is this a bug or just poor documentation?
Using the IntentService coupled with the FusedLocationProviderAPI will present issues. From the Developer Docs titled Receiving Location Updates:
Depending on the form of the request, the fused location provider
either invokes the LocationListener.onLocationChanged() callback
method and passes it a Location object, or issues a PendingIntent that
contains the location in its extended data. The accuracy and frequency
of the updates are affected by the location permissions you've
requested and the options you set in the location request object
Further, a PendingIntent is used for extending permissions for another piece of code (FusedLocationProviderAPI in Google Play Services) to execute their code within your apk. An IntentService is used to start a Service defined within the scope of your apk.
So, the method requires an implementation of LocationListener for foreground updates, or a PendingIntent for background updates coupled with a Broadcast Receiver.
This is a working example of some methods used to request location updates from a PendingIntent coupled with extra values.
Note: LocalStorage.java is a utility class for storing local variables, it is not part of the Android API
GPSPlotter
/**
* Private helper method to initialize the Google Api Client with the
* LocationServices Api and Build it for use.
*/
private void initializeGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
/**
* Private helper method to determine whether or not GooglePlayServices
* are installed on the local system.
*
* #return services are installed.
*/
private boolean googlePlayServicesInstalled() {
int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mContext);
return result == ConnectionResult.SUCCESS;
}
/**
* Private method to build the Api Client for use with the LocationServices API.
*/
private synchronized void buildApiClient() {
Log.w(TAG, "Building Google Api Client...");
initializeGoogleApiClient();
}
/**
* Private method used to connect the ApiClient to the Api hosted by Google for
* Accessing Locations.
*/
private void connectClient() {
mGoogleApiClient.connect();
}
/**
* User passes in a requested interval polling time in seconds as an
* integer.
*
* #param theAccount is a reference to the parent activity used for updating views.
*/
public void beginManagedLocationRequests(MyAccount theAccount) {
if (mAccount == null)
mAccount = theAccount;
startBackgroundUpdates();
}
/**
* Public method to end the managed Location Requests.
*/
public void endManagedLocationRequests() {
endBackgroundUpdates();
}
/**
* This method handles the switch in polling rates by stopping and then starting once more the
* background udpates, which in turn sets the interval in another method in the call stack.
* #param theInterval the desired interval polling rate
*/
public void changeRequestIntervals(int theInterval) {
mIntentInterval = theInterval;
if (LocalStorage.getRequestingBackgroundStatus(mContext)) {
endBackgroundUpdates();
startBackgroundUpdates();
}
}
/**
* Private helper method to build an Intent that will be couple with a pending intent uses
* for issuing background Location requests.
*
* #return theIntent
*/
private Intent buildBackgroundRequestIntent() {
Intent intent = new Intent(mContext, BackgroundLocationReceiver.class);
intent.setAction(BACKGROUND_ACTION);
intent.putExtra(User.USER_ID, mUserID);
return intent;
}
/**
* Private helper method used to generate a PendingIntent for use when the User requests background service
* within the FusedLocationApi until the Interval is changed.
*
* #return pendingIntent
*/
private PendingIntent buildRequestPendingIntent(Intent theIntent) {
Log.w(TAG, "building pending intent");
return PendingIntent.getBroadcast(mContext, 0, theIntent, 0);
}
/**
* Private method to start the Location Updates using the FusedLocation API in the background.
*/
private void startBackgroundUpdates() {
Log.w(TAG, "Starting background updates");
if (googlePlayServicesInstalled()) {
LocalStorage.putBackgroundRequestStatus(true, mContext);
LocalStorage.putLocationRequestStatus(true, mContext);
registerAlarmManager();
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, buildLocationRequest(), buildRequestPendingIntent(buildBackgroundRequestIntent()));
}
}
/**
* Private method to end background updates.
*/
private void endBackgroundUpdates() {
Log.w(TAG, "Ending background updates");
LocalStorage.putBackgroundRequestStatus(false, mContext);
LocalStorage.putLocationRequestStatus(false, mContext);
unregisterAlarmManager();
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, buildRequestPendingIntent(buildBackgroundRequestIntent()));
}
BackgroundLocationReceiver
public class BackgroundLocationReceiver extends BroadcastReceiver {
private static final String TAG = "BLocRec: ";
private static final String UPLOAD_ERROR_MESSAGE = "Background Service to Upload Coordinates Failed.";
private static final String UPLOAD_MESSAGE = "Coordinate Batch Pushed to Database.";
public BackgroundLocationReceiver() {
//Default, no-arg constructor
}
/**
* This method handles any location updates received when the app is no longer in focus. Coordinates are
* stored in the local database and uploaded once every hour.
* #param context the application context
* #param intent is the pending intent
*/
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().matches(GPSPlotter.BACKGROUND_ACTION)) {
Log.w(TAG, "BLR Received-background");
Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
storeLocation(location, context, intent.getStringExtra(User.USER_ID));
}
EDIT
The method below builds a LocationRequest necessary for invoking the requestLocationUpdates() method
/**
* Private helper method used to generate a LocationRequest which will be used to handle all location updates
* within the FusedLocationApi until the Interval is changed.
*
* #return locationRequest
*/
private LocationRequest buildLocationRequest() {
int dateConversion = 1000;
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(mIntentInterval * dateConversion);
locationRequest.setFastestInterval((mIntentInterval / 2) * dateConversion);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
Log.w(TAG, "Building location request");
return locationRequest;
}
EDIT
After a long discussion in chat with Catherine, we came to the conclusion that google play services library 7.5 has a bug that does not process the Parcelable Extra Location passed from FusedLocationProviderAPI when other extras are put into the Intent. However, 7.0 does provide this capability. She said that she will submit a bug and we'll see how long it takes the Android team to resolve
This question already has answers here:
How to check if Location Services are enabled?
(23 answers)
Closed 7 years ago.
I am using Android Geofencing API.After add geofence it works fine when location is ON.But If I OFF location and ON again,afterwards Geofencing not triggers when enter or exit.I have tested on my Moto G with Android 5.0.2 device. Is it Geofence will expire If location is off?
I have seen on Android document as,
In case network location provider is disabled by the user, the
geofence service will stop updating, all registered geofences will be
removed and an intent is generated by the provided pending intent. In
this case, the GeofencingEvent created from this intent represents an
error event, where hasError() returns true and getErrorCode() returns
GEOFENCE_NOT_AVAILABLE.
Document link
Yeap, geofences will be removed once location is switched off.
You should listen for location provider broadcasts using a BroadcastReceiver in your manifest, something like:
<receiver android:name="your.Receiver">
<intent-filter>
<action android:name="android.location.PROVIDERS_CHANGED"/>
</intent-filter>
</receiver>
and then register your geofences again.
public class LocationProviderReceiver extends BroadcastReceiver {
public LocationProviderReceiver() {
}
#Override
public void onReceive(Context context, Intent intent) {
if (action.equals(LocationManager.PROVIDERS_CHANGED_ACTION)) {
// if location services enabled
// restart geofences
}
}
}
Add this to the manifest:
<receiver android:name="com.aol.android.geofence.GeofenceReceiver"
android:exported="false">
<intent-filter >
<action android:name="com.aol.android.geofence.ACTION_RECEIVE_GEOFENCE"/>
</intent-filter>
</receiver>
Then in the GeofenceRequester class you need to change the createRequestPendingIntent method so that it goes to your BroadcastReceiver instead of the ReceiveTransitionsIntentService
private PendingIntent createRequestPendingIntent() {
// If the PendingIntent already exists
if (null != mGeofencePendingIntent) {
// Return the existing intent
return mGeofencePendingIntent;
// If no PendingIntent exists
} else {
// Create an Intent pointing to the IntentService
Intent intent = new Intent("com.aol.android.geofence.ACTION_RECEIVE_GEOFENCE");
// Intent intent = new Intent(context, ReceiveTransitionsIntentService.class);
/*
* Return a PendingIntent to start the IntentService.
* Always create a PendingIntent sent to Location Services
* with FLAG_UPDATE_CURRENT, so that sending the PendingIntent
* again updates the original. Otherwise, Location Services
* can't match the PendingIntent to requests made with it.
*/
return PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}
Then I added the GeofenceReceiver class that looks something like this:
public class GeofenceReceiver extends BroadcastReceiver {
Context context;
Intent broadcastIntent = new Intent();
#Override
public void onReceive(Context context, Intent intent) {
this.context = context;
broadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES);
if (LocationClient.hasError(intent)) {
handleError(intent);
} else {
handleEnterExit(intent);
}
}
private void handleError(Intent intent){
// Get the error code
int errorCode = LocationClient.getErrorCode(intent);
// Get the error message
String errorMessage = LocationServiceErrorMessages.getErrorString(
context, errorCode);
// Log the error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_error_detail,
errorMessage));
// Set the action and error message for the broadcast intent
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, errorMessage);
// Broadcast the error *locally* to other components in this app
LocalBroadcastManager.getInstance(context).sendBroadcast(
broadcastIntent);
}
private void handleEnterExit(Intent intent) {
// Get the type of transition (entry or exit)
int transition = LocationClient.getGeofenceTransition(intent);
// Test that a valid transition was reported
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)
|| (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
// Post a notification
List<Geofence> geofences = LocationClient
.getTriggeringGeofences(intent);
String[] geofenceIds = new String[geofences.size()];
String ids = TextUtils.join(GeofenceUtils.GEOFENCE_ID_DELIMITER,
geofenceIds);
String transitionType = GeofenceUtils
.getTransitionString(transition);
for (int index = 0; index < geofences.size(); index++) {
Geofence geofence = geofences.get(index);
...do something with the geofence entry or exit. I'm saving them to a local sqlite db
}
// Create an Intent to broadcast to the app
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_TRANSITION)
.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_ID, geofenceIds)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_TRANSITION_TYPE,
transitionType);
LocalBroadcastManager.getInstance(MyApplication.getContext())
.sendBroadcast(broadcastIntent);
// Log the transition type and a message
Log.d(GeofenceUtils.APPTAG, transitionType + ": " + ids);
Log.d(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_notification_text));
// In debug mode, log the result
Log.d(GeofenceUtils.APPTAG, "transition");
// An invalid transition was reported
} else {
// Always log as an error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_invalid_type,
transition));
}
}
/**
* Posts a notification in the notification bar when a transition is
* detected. If the user clicks the notification, control goes to the main
* Activity.
*
* #param transitionType
* The type of transition that occurred.
*
*/
private void sendNotification(String transitionType, String locationName) {
// Create an explicit content Intent that starts the main Activity
Intent notificationIntent = new Intent(context, MainActivity.class);
// Construct a task stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the main Activity to the task stack as the parent
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack
PendingIntent notificationPendingIntent = stackBuilder
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions
// >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(
context);
// Set the notification contents
builder.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(transitionType + ": " + locationName)
.setContentText(
context.getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Get an instance of the Notification manager
NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
}
I am using the sample code(Scheduler.zip) available on the Android developer training website- http://developer.android.com/training/scheduling/index.html
Here's the code:-
MainActivity.java
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.scheduler;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
/**
* This sample demonstrates how to schedule an alarm that causes a service to
* be started. This is useful when you want to schedule alarms that initiate
* long-running operations, such as retrieving a daily forecast.
* This particular sample retrieves content from the Google home page once a day and
* checks it for the search string "doodle". If it finds this string, that indicates
* that the page contains a custom doodle instead of the standard Google logo.
*/
public class MainActivity extends Activity {
SampleAlarmReceiver alarm = new SampleAlarmReceiver();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
// Menu options to set and cancel the alarm.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// When the user clicks START ALARM, set the alarm.
case R.id.start_action:
alarm.setAlarm(this);
return true;
// When the user clicks CANCEL ALARM, cancel the alarm.
case R.id.cancel_action:
alarm.cancelAlarm(this);
return true;
}
return false;
}
}
SampleAlarmReceiver.java
package com.example.android.scheduler;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.support.v4.content.WakefulBroadcastReceiver;
import java.util.Calendar;
/**
* When the alarm fires, this WakefulBroadcastReceiver receives the broadcast Intent
* and then starts the IntentService {#code SampleSchedulingService} to do some work.
*/
public class SampleAlarmReceiver extends WakefulBroadcastReceiver {
// The app's AlarmManager, which provides access to the system alarm services.
private AlarmManager alarmMgr;
// The pending intent that is triggered when the alarm fires.
private PendingIntent alarmIntent;
#Override
public void onReceive(Context context, Intent intent) {
// BEGIN_INCLUDE(alarm_onreceive)
/*
* If your receiver intent includes extras that need to be passed along to the
* service, use setComponent() to indicate that the service should handle the
* receiver's intent. For example:
*
* ComponentName comp = new ComponentName(context.getPackageName(),
* MyService.class.getName());
*
* // This intent passed in this call will include the wake lock extra as well as
* // the receiver intent contents.
* startWakefulService(context, (intent.setComponent(comp)));
*
* In this example, we simply create a new intent to deliver to the service.
* This intent holds an extra identifying the wake lock.
*/
Intent service = new Intent(context, SampleSchedulingService.class);
// Start the service, keeping the device awake while it is launching.
startWakefulService(context, service);
// END_INCLUDE(alarm_onreceive)
}
// BEGIN_INCLUDE(set_alarm)
/**
* Sets a repeating alarm that runs once a day at approximately 8:30 a.m. When the
* alarm fires, the app broadcasts an Intent to this WakefulBroadcastReceiver.
* #param context
*/
public void setAlarm(Context context) {
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, SampleAlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
// Set the alarm's trigger time to 8:30 a.m.
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);
/*
* If you don't have precise time requirements, use an inexact repeating alarm
* the minimize the drain on the device battery.
*
* The call below specifies the alarm type, the trigger time, the interval at
* which the alarm is fired, and the alarm's associated PendingIntent.
* It uses the alarm type RTC_WAKEUP ("Real Time Clock" wake up), which wakes up
* the device and triggers the alarm according to the time of the device's clock.
*
* Alternatively, you can use the alarm type ELAPSED_REALTIME_WAKEUP to trigger
* an alarm based on how much time has elapsed since the device was booted. This
* is the preferred choice if your alarm is based on elapsed time--for example, if
* you simply want your alarm to fire every 60 minutes. You only need to use
* RTC_WAKEUP if you want your alarm to fire at a particular date/time. Remember
* that clock-based time may not translate well to other locales, and that your
* app's behavior could be affected by the user changing the device's time setting.
*
* Here are some examples of ELAPSED_REALTIME_WAKEUP:
*
* // Wake up the device to fire a one-time alarm in one minute.
* alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
* SystemClock.elapsedRealtime() +
* 60*1000, alarmIntent);
*
* // Wake up the device to fire the alarm in 30 minutes, and every 30 minutes
* // after that.
* alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
* AlarmManager.INTERVAL_HALF_HOUR,
* AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
*/
// Set the alarm to fire at approximately 8:30 a.m., according to the device's
// clock, and to repeat once a day.
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(), 2*60*1000, alarmIntent);
// Enable {#code SampleBootReceiver} to automatically restart the alarm when the
// device is rebooted.
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
// END_INCLUDE(set_alarm)
/**
* Cancels the alarm.
* #param context
*/
// BEGIN_INCLUDE(cancel_alarm)
public void cancelAlarm(Context context) {
// If the alarm has been set, cancel it.
if (alarmMgr!= null) {
alarmMgr.cancel(alarmIntent);
}
// Disable {#code SampleBootReceiver} so that it doesn't automatically restart the
// alarm when the device is rebooted.
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
// END_INCLUDE(cancel_alarm)
}
SampleBootReceiver.java
package com.example.android.scheduler;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* This BroadcastReceiver automatically (re)starts the alarm when the device is
* rebooted. This receiver is set to be disabled (android:enabled="false") in the
* application's manifest file. When the user sets the alarm, the receiver is enabled.
* When the user cancels the alarm, the receiver is disabled, so that rebooting the
* device will not trigger this receiver.
*/
// BEGIN_INCLUDE(autostart)
public class SampleBootReceiver extends BroadcastReceiver {
SampleAlarmReceiver alarm = new SampleAlarmReceiver();
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED"))
{
alarm.setAlarm(context);
}
}
}
//END_INCLUDE(autostart)
SampleSchedulingService.java
package com.example.android.scheduler;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* This {#code IntentService} does the app's actual work.
* {#code SampleAlarmReceiver} (a {#code WakefulBroadcastReceiver}) holds a
* partial wake lock for this service while the service does its work. When the
* service is finished, it calls {#code completeWakefulIntent()} to release the
* wake lock.
*/
public class SampleSchedulingService extends IntentService {
public SampleSchedulingService() {
super("SchedulingService");
}
public static final String TAG = "Scheduling Demo";
// An ID used to post the notification.
public static final int NOTIFICATION_ID = 1;
// The string the app searches for in the Google home page content. If the app finds
// the string, it indicates the presence of a doodle.
public static final String SEARCH_STRING = "doodle";
// The Google home page URL from which the app fetches content.
// You can find a list of other Google domains with possible doodles here:
// http://en.wikipedia.org/wiki/List_of_Google_domains
public static final String URL = "http://www.google.com";
private NotificationManager mNotificationManager;
NotificationCompat.Builder builder;
#Override
protected void onHandleIntent(Intent intent) {
// BEGIN_INCLUDE(service_onhandle)
// The URL from which to fetch content.
String urlString = URL;
String result ="";
// Try to connect to the Google homepage and download content.
try {
result = loadFromNetwork(urlString);
} catch (IOException e) {
Log.i(TAG, getString(R.string.connection_error));
}
// If the app finds the string "doodle" in the Google home page content, it
// indicates the presence of a doodle. Post a "Doodle Alert" notification.
if (result.indexOf(SEARCH_STRING) != -1) {
sendNotification(getString(R.string.doodle_found));
Log.i(TAG, "Found doodle!!");
} else {
sendNotification(getString(R.string.no_doodle));
Log.i(TAG, "No doodle found. :-(");
}
// Release the wake lock provided by the BroadcastReceiver.
SampleAlarmReceiver.completeWakefulIntent(intent);
// END_INCLUDE(service_onhandle)
}
// Post a notification indicating whether a doodle was found.
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager)
this.getSystemService(Context.NOTIFICATION_SERVICE);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(getString(R.string.doodle_alert))
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
//
// The methods below this line fetch content from the specified URL and return the
// content as a string.
//
/** Given a URL string, initiate a fetch operation. */
private String loadFromNetwork(String urlString) throws IOException {
InputStream stream = null;
String str ="";
try {
stream = downloadUrl(urlString);
str = readIt(stream);
} finally {
if (stream != null) {
stream.close();
}
}
return str;
}
/**
* Given a string representation of a URL, sets up a connection and gets
* an input stream.
* #param urlString A string representation of a URL.
* #return An InputStream retrieved from a successful HttpURLConnection.
* #throws IOException
*/
private InputStream downloadUrl(String urlString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Start the query
conn.connect();
InputStream stream = conn.getInputStream();
return stream;
}
/**
* Reads an InputStream and converts it to a String.
* #param stream InputStream containing HTML from www.google.com.
* #return String version of InputStream.
* #throws IOException
*/
private String readIt(InputStream stream) throws IOException {
StringBuilder builder = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
for(String line = reader.readLine(); line != null; line = reader.readLine())
builder.append(line);
reader.close();
return builder.toString();
}
}
I had to add the android-support-v4.jar file to the build path first of all to make it work.
I am getting notifications after every 2 mins, but on clicking the "Cancel Alarm" button, the alarm is not getting canceled.
So , I tried removing the if condition in the cancelAlarm function, but I then got a java.nullpointerexception.
So basically, the alarmMgr is becoming null.
But, I am not able to understand why.
Also, since this is a sample code, I think there must be no errors in it, so I must be missing out on something.
I think it is because the instance of alarmManager (alarmMgr) could be uninitialized, as it is initialized in setAlarm. So you have to initialize it also in the cancelAlarm method, in case it is null.
SampleAlarmReceiver.java:
public void cancelAlarm(Context context) {
if (alarmMgr==null) {
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
}
Intent intent = new Intent(context, SampleAlarmReceiver.class);
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0 , intent, 0);
try{
alarmMgr.cancel(alarmIntent);
Log.e("Alarma cancelada", action );
}catch (Exception e) {
Log.e("Error alarma", "AlarmManager update was not canceled. " + e.toString());
}
.
.
.
I am having some trouble with activity recognition. I have implemented it in an app and it works fine when the device's screen is on. I have a log entry in my Activity recognition intent service class and I can see when it gets an update.So, I know it is working fine when the screen is on.
But then after the phone is put to standby(the screen is turned off) it stops detecting the uses activity.
The onDisconnected() in the DetectionRequester class is not getting called, I checked using a log post.
My question: Why does my app stop tracking the uses activity after the device goes to standby mode? And how do I make it not stop detecting the users activity?
Let me know if you need to see any of the code or if you need any more details on my app.
Relevant bits of code from my MainActivity class. This is where the app starts the request for ActivityRecognition.
public class MainActivity extends FragmentActivity {
// The activity recognition update request object
private DetectionRequester mDetectionRequester;
// The activity recognition update removal object
private DetectionRemover mDetectionRemover;
// Store the current request type (ADD or REMOVE)
private REQUEST_TYPE mRequestType;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Crashlytics.start(this);
setContentView(R.layout.activity_main);
mDetectionRequester = new DetectionRequester(this);
mDetectionRemover = new DetectionRemover(this);
// Check for Google Play services
if (servicesConnected())
{
/*
*Set the request type. If a connection error occurs, and Google Play services can
* handle it, then onActivityResult will use the request type to retry the request
*/
mRequestType = ActivityUtils.REQUEST_TYPE.ADD;
// Pass the update request to the requester object
mDetectionRequester.requestUpdates();
}
}
private boolean servicesConnected() {
Log.wtf("Rakshak", "Service connected method");
// Check that Google Play services is available
int resultCode =
GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
// If Google Play services is available
if (ConnectionResult.SUCCESS == resultCode)
{
Log.wtf("Rakshak", "Service connected method: connection result success");
// Continue
return true;
// Google Play services was not available for some reason
} else {
Log.wtf("Rakshak", "Service connected method: connection result failure");
// Display an error dialog
GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0).show();
return false;
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
// Choose what to do based on the request code
switch (requestCode) {
// If the request code matches the code sent in onConnectionFailed
case ActivityUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST :
switch (resultCode) {
// If Google Play services resolved the problem
case Activity.RESULT_OK:
// If the request was to start activity recognition updates
if (ActivityUtils.REQUEST_TYPE.ADD == mRequestType) {
// Restart the process of requesting activity recognition updates
mDetectionRequester.requestUpdates();
// If the request was to remove activity recognition updates
} else if (ActivityUtils.REQUEST_TYPE.REMOVE == mRequestType ){
/*
* Restart the removal of all activity recognition updates for the
* PendingIntent.
*/
// mDetectionRemover.removeUpdates(
// mDetectionRequester.getRequestPendingIntent());
}
break;
// If any other result was returned by Google Play services
default:
// Report that Google Play services was unable to resolve the problem.
Log.d(ActivityUtils.APPTAG, "unable to resolve Google play services problems");
}
// If any other request code was received
default:
// Report that this Activity received an unknown requestCode
Log.d(ActivityUtils.APPTAG,
"received an unknown request code");
break;
}
}
My DetectionRequester class:
public class DetectionRequester
implements ConnectionCallbacks, OnConnectionFailedListener {
// Storage for a context from the calling client
private Context mContext;
// Stores the PendingIntent used to send activity recognition events back to the app
private PendingIntent mActivityRecognitionPendingIntent;
// Stores the current instantiation of the activity recognition client
private ActivityRecognitionClient mActivityRecognitionClient;
public DetectionRequester(Context context) {
// Save the context
mContext = context;
// Initialize the globals to null
mActivityRecognitionPendingIntent = null;
mActivityRecognitionClient = null;
}
/**
* Returns the current PendingIntent to the caller.
*
* #return The PendingIntent used to request activity recognition updates
*/
public PendingIntent getRequestPendingIntent() {
return mActivityRecognitionPendingIntent;
}
/**
* Sets the PendingIntent used to make activity recognition update requests
* #param intent The PendingIntent
*/
public void setRequestPendingIntent(PendingIntent intent) {
mActivityRecognitionPendingIntent = intent;
}
/**
* Start the activity recognition update request process by
* getting a connection.
*/
public void requestUpdates() {
requestConnection();
}
/**
* Make the actual update request. This is called from onConnected().
*/
private void continueRequestActivityUpdates() {
/*
* Request updates, using the default detection interval.
* The PendingIntent sends updates to ActivityRecognitionIntentService
*/
getActivityRecognitionClient().requestActivityUpdates(
ActivityUtils.DETECTION_INTERVAL_MILLISECONDS,
createRequestPendingIntent());
// Disconnect the client
requestDisconnection();
}
/**
* Request a connection to Location Services. This call returns immediately,
* but the request is not complete until onConnected() or onConnectionFailure() is called.
*/
private void requestConnection() {
getActivityRecognitionClient().connect();
}
/**
* Get the current activity recognition client, or create a new one if necessary.
* This method facilitates multiple requests for a client, even if a previous
* request wasn't finished. Since only one client object exists while a connection
* is underway, no memory leaks occur.
*
* #return An ActivityRecognitionClient object
*/
private ActivityRecognitionClient getActivityRecognitionClient() {
if (mActivityRecognitionClient == null) {
mActivityRecognitionClient =
new ActivityRecognitionClient(mContext, this, this);
}
return mActivityRecognitionClient;
}
/**
* Get the current activity recognition client and disconnect from Location Services
*/
private void requestDisconnection() {
getActivityRecognitionClient().disconnect();
}
/*
* Called by Location Services once the activity recognition client is connected.
*
* Continue by requesting activity updates.
*/
#Override
public void onConnected(Bundle arg0) {
// If debugging, log the connection
Log.w("Rakshak", "Locatin client connected");
// Continue the process of requesting activity recognition updates
continueRequestActivityUpdates();
}
/*
* Called by Location Services once the activity recognition client is disconnected.
*/
#Override
public void onDisconnected() {
// In debug mode, log the disconnection
Log.w("Rakshak", "Locatin client dis-connected");
// Destroy the current activity recognition client
mActivityRecognitionClient = null;
}
/**
* Get a PendingIntent to send with the request to get activity recognition updates. Location
* Services issues the Intent inside this PendingIntent whenever a activity recognition update
* occurs.
*
* #return A PendingIntent for the IntentService that handles activity recognition updates.
*/
private PendingIntent createRequestPendingIntent() {
// If the PendingIntent already exists
if (null != getRequestPendingIntent()) {
// Return the existing intent
return mActivityRecognitionPendingIntent;
// If no PendingIntent exists
} else {
// Create an Intent pointing to the IntentService
Intent intent = new Intent(mContext, ActivityRecognitionIntentService.class);
/*
* Return a PendingIntent to start the IntentService.
* Always create a PendingIntent sent to Location Services
* with FLAG_UPDATE_CURRENT, so that sending the PendingIntent
* again updates the original. Otherwise, Location Services
* can't match the PendingIntent to requests made with it.
*/
PendingIntent pendingIntent = PendingIntent.getService(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
setRequestPendingIntent(pendingIntent);
return pendingIntent;
}
}
/*
* Implementation of OnConnectionFailedListener.onConnectionFailed
* If a connection or disconnection request fails, report the error
* connectionResult is passed in from Location Services
*/
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
/*
* Google Play services can resolve some errors it detects.
* If the error has a resolution, try sending an Intent to
* start a Google Play services activity that can resolve
* error.
*/
if (connectionResult.hasResolution()) {
try {
connectionResult.startResolutionForResult((Activity) mContext,
ActivityUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
} catch (SendIntentException e) {
// display an error or log it here.
}
/*
* If no resolution is available, display Google
* Play service error dialog. This may direct the
* user to Google Play Store if Google Play services
* is out of date.
*/
} else {
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(
connectionResult.getErrorCode(),
(Activity) mContext,
ActivityUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST);
if (dialog != null) {
dialog.show();
}
}
}
}
The intent service:
public class ActivityRecognitionIntentService extends IntentService {
public ActivityRecognitionIntentService() {
// Set the label for the service's background thread
super("ActivityRecognitionIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.w("Rakshak", "the on handel intent called"); // I see this only when the devices screen is on
// do some fun stuff
}
To avoid draining the battery, an Android device that is left idle quickly falls asleep. It's the reason of your service stopping. You can read more about how it can be handled here: Keeping the Device Awake. Also take a look on WakefulIntentService from cwac-wakeful library. Looks like it's what you are looking for.
Take a look at "Repeating Alarms":
They operate outside of your application, so you can use them to trigger events or actions even when your app is not running, and even if the device itself is asleep.
https://developer.android.com/training/scheduling/alarms.html
Or at "WakeLock":
One legitimate case for using a wake lock might be a background service that needs to grab a wake lock to keep the CPU running to do work while the screen is off. Again, though, this practice should be minimized because of its impact on battery life.
Specially "WakefulBroadcastReceiver":
Using a broadcast receiver in conjunction with a service lets you manage the life cycle of a background task.
A WakefulBroadcastReceiver is a special type of broadcast receiver that takes care of creating and managing a PARTIAL_WAKE_LOCK for your app. A WakefulBroadcastReceiver passes off the work to a Service (typically an IntentService), while ensuring that the device does not go back to sleep in the transition. If you don't hold a wake lock while transitioning the work to a service, you are effectively allowing the device to go back to sleep before the work completes. The net result is that the app might not finish doing the work until some arbitrary point in the future, which is not what you want.
https://developer.android.com/training/scheduling/wakelock.html#cpu
If you consider to use the second approach be careful about draining the battery...
I use Geofences in my app, everything works fine except the removal of triggered geofences.
I red the guide from the official documentation of Android but they don't explain how to remove a geofence inside of the IntentService.
Here is the code of the event handler of the service:
#Override
protected void onHandleIntent(Intent intent)
{
Log.e("GeofenceIntentService", "Location handled");
if (LocationClient.hasError(intent))
{
int errorCode = LocationClient.getErrorCode(intent);
Log.e("GeofenceIntentService", "Location Services error: " + Integer.toString(errorCode));
}
else
{
int transitionType = LocationClient.getGeofenceTransition(intent);
if (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER)
{
List <Geofence> triggerList = LocationClient.getTriggeringGeofences(intent);
String[] triggerIds = new String[triggerList.size()];
for (int i = 0; i < triggerIds.length; i++)
{
// Store the Id of each geofence
triggerIds[i] = triggerList.get(i).getRequestId();
Picture p = PicturesManager.getById(triggerIds[i], getApplicationContext());
/* ... do a lot of work here ... */
}
}
else
Log.e("ReceiveTransitionsIntentService", "Geofence transition error: " + Integer.toString(transitionType));
}
}
How can I delete the geofence after he got triggered ?
You can do something like this:
LocationServices.GeofencingApi.removeGeofences(
mGoogleApiClient,
// This is the same pending intent that was used in addGeofences().
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
And your getGeofencePendingIntent() method can look like this:
/**
* Gets a PendingIntent to send with the request to add or remove Geofences. Location Services
* issues the Intent inside this PendingIntent whenever a geofence transition occurs for the
* current list of geofences.
*
* #return A PendingIntent for the IntentService that handles geofence transitions.
*/
private PendingIntent getGeofencePendingIntent() {
Log.d(TAG, "getGeofencePendingIntent()");
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(mContext, GeofenceIntentServiceStub.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
mGeofencePendingIntent = PendingIntent.getService(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return mGeofencePendingIntent;
}
You would proceed as you did when adding Geofences (create a LocationClient and wait for it to connect). Once it is connected, in the onConnected callback method, you would call removeGeofences on the LocationClient instance instead and pass it a list of request IDs you want to remove and an instance of OnRemoveGeofencesResultListener as a callback handler.
Of course, you must use the same request IDs you used when creating the GeoFence with GeoFence.Builder's setRequestId.
#Override
public void onConnected(Bundle arg0) {
locationClient.removeGeofences(requestIDsList,
new OnRemoveGeofencesResultListener() {
...
});