I've created a class and a BroadcastReceiver to get callbacks from the awareness api for when walking or running ends.
I wasn't getting timely callbacks and at first thought it was because I had registered a 'stopping' callback, but then after setting my phone down for a bit, I did get several callbacks! But this was far far from the time I'd stopped walking. At least 5 minutes after stopping.
Sometimes I don't get callbacks even when the Google Fit app records activity.
Since I have gotten callbacks at least a few times I know the registration is ok. Why are the calls this delayed and sometimes missing?
For background reference, I'm registering these callbacks in the onStart on the main activty, i.e initiateAwareness is called at that time, within the onstart on an activity. And i never unregister them.
I don't intend to use it this way in production, it was just for testing. Plus my inital attempt of registering the fences with an application context failed.
Here's the helper class I made to set up the registration of the google client and fences.
public class AwarenessHelper {
public static final String WALKING_ENDED_FENCE = "walkingEndedKey";
public static final String RUNNING_ENDED_FENCE = "runningEndedKey";
public static final String TYPE_2_WALKING = "duringWalkingKey";
public static final String TYPE_2_RUNNING = "duringRunningKey";
private String tag = AwarenessHelper.class.getSimpleName();
public void initiateAwareness(final Activity context)
{
final GoogleApiClient googleApiClient = buildClient(context);
Log.d(tag, "Initiating blocking connect");
googleApiClient.registerConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(#Nullable Bundle bundle) {
if ( googleApiClient.isConnected() )
{
Log.d(tag, "Client connected, initiating awareness fence registration");
registerAwarenessFences(context, googleApiClient);
}
else
{
Log.d(tag, "Couldn't connect");
}
}
#Override
public void onConnectionSuspended(int i) {
}
});
googleApiClient.connect();
}
private void registerAwarenessFences(Context context, GoogleApiClient mGoogleApiClient) {
Awareness.FenceApi.updateFences(
mGoogleApiClient,
new FenceUpdateRequest.Builder()
.addFence(WALKING_ENDED_FENCE, DetectedActivityFence.stopping(DetectedActivityFence.WALKING), getBroadcastPendingIntent(context))
.addFence(RUNNING_ENDED_FENCE, DetectedActivityFence.stopping(DetectedActivityFence.RUNNING), getBroadcastPendingIntent(context))
.addFence(TYPE_2_WALKING, DetectedActivityFence.during(DetectedActivityFence.WALKING), getBroadcastPendingIntent(context))
.addFence(TYPE_2_RUNNING, DetectedActivityFence.stopping(DetectedActivityFence.RUNNING), getBroadcastPendingIntent(context))
.build())
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(tag, "Fence was successfully registered.");
} else {
Log.e(tag, "Fence could not be registered: " + status);
}
}
});
}
private GoogleApiClient buildClient(final Activity activity)
{
GoogleApiClient client = new GoogleApiClient.Builder(activity)
.addApi(Awareness.API)
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
if ( connectionResult.hasResolution() && connectionResult.getErrorCode() == CommonStatusCodes.SIGN_IN_REQUIRED )
{
try {
connectionResult.startResolutionForResult(activity, GOOGLE_FIT_AUTHORIZATION_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
}
})
.build();
return client;
}
private PendingIntent getBroadcastPendingIntent(Context context)
{
Intent intent = new Intent(AWARENESS_BROADCAST_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
return pendingIntent;
}
}
Here's the BroadcastReceiver:
public class AwarenessHelper {
public static final String WALKING_ENDED_FENCE = "walkingEndedKey";
public static final String RUNNING_ENDED_FENCE = "runningEndedKey";
public static final String TYPE_2_WALKING = "duringWalkingKey";
public static final String TYPE_2_RUNNING = "duringRunningKey";
private String tag = AwarenessHelper.class.getSimpleName();
public void initiateAwareness(final Activity context)
{
final GoogleApiClient googleApiClient = buildClient(context);
Log.d(tag, "Initiating blocking connect");
googleApiClient.registerConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(#Nullable Bundle bundle) {
if ( googleApiClient.isConnected() )
{
Log.d(tag, "Client connected, initiating awareness fence registration");
registerAwarenessFences(context, googleApiClient);
}
else
{
Log.d(tag, "Couldn't connect");
}
}
#Override
public void onConnectionSuspended(int i) {
}
});
googleApiClient.connect();
}
private void registerAwarenessFences(Context context, GoogleApiClient mGoogleApiClient) {
Awareness.FenceApi.updateFences(
mGoogleApiClient,
new FenceUpdateRequest.Builder()
.addFence(WALKING_ENDED_FENCE, DetectedActivityFence.stopping(DetectedActivityFence.WALKING), getBroadcastPendingIntent(context))
.addFence(RUNNING_ENDED_FENCE, DetectedActivityFence.stopping(DetectedActivityFence.RUNNING), getBroadcastPendingIntent(context))
.addFence(TYPE_2_WALKING, DetectedActivityFence.during(DetectedActivityFence.WALKING), getBroadcastPendingIntent(context))
.addFence(TYPE_2_RUNNING, DetectedActivityFence.stopping(DetectedActivityFence.RUNNING), getBroadcastPendingIntent(context))
.build())
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(tag, "Fence was successfully registered.");
} else {
Log.e(tag, "Fence could not be registered: " + status);
}
}
});
}
private GoogleApiClient buildClient(final Activity activity)
{
GoogleApiClient client = new GoogleApiClient.Builder(activity)
.addApi(Awareness.API)
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
if ( connectionResult.hasResolution() && connectionResult.getErrorCode() == CommonStatusCodes.SIGN_IN_REQUIRED )
{
try {
connectionResult.startResolutionForResult(activity, GOOGLE_FIT_AUTHORIZATION_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
}
})
.build();
return client;
}
private PendingIntent getBroadcastPendingIntent(Context context)
{
Intent intent = new Intent(AWARENESS_BROADCAST_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
return pendingIntent;
}
}
I get the notifications, but after a massive delay and sometimes not at all.
I am starting the Activity many times, so perhaps the fences are being registered over and over? Is that a relevant fact?
Also is a service or broadcast receiver context appropriate for the initialisation of awareness clients and fences?
Awareness subscribes to get ActivityRecognition updates rather infrequently, so it is not very unexpected that you get a response after a few minutes.
You should also worry about having too many fences without unregistering in general.
Also there is no reason to have a separate pendingIntent for each of your fences; you could have a single pendingIntent and add all fences against that one. Use fence key extra to distinguish results of each fence. And again, do unregister when it makes sense. Otherwise, the fences could hang around even after your app goes away.
Related
I'm making an android application in which want to create a Geofence at LatLong coordinates specified at my server.
The coordinates are getting updated periodically in background from within a service. I'm reading these coordinates and wish to create a Geofence for it in background without user intervention. I tried doing so within a service but it's not working. I can get the coordinates from the server at regular intervals but I'm not able to create Geofence for it, getting a nullPointerException.
However, I'm able to create Geofences in an Activity on click of a button(hardcoding LatLong coordinates) but its not working in a service.
My service:
public class GPSPollingService extends Service implements ResultCallback<Status>, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
private GoogleApiClient mGoogleApiClient;
private PowerManager.WakeLock mWakeLock;
private LocationRequest mLocationRequest;
// Flag that indicates if a request is underway.
private boolean mInProgress;
private final String TAG = "GPSPollingService";
private PendingIntent mGeofencePendingIntent;
String url = "http://shielded.coolpage.biz/status_read.php";
private static final int REQUEST_CODE_LOCATION = 2;
private Boolean servicesAvailable = false;
private Activity mActivity;
ArrayList<Geofence> mCurrentGeofences = new ArrayList<Geofence>();;
RequestQueue requestQueue;
Location mLocation;
public GPSPollingService(){}
public GPSPollingService(Activity mActivity){
super();
this.mActivity = mActivity;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
mInProgress = false;
requestQueue = Volley.newRequestQueue(this);
// Instantiate the current List of geofences
mCurrentGeofences = new ArrayList<Geofence>();
mGeofencePendingIntent = null;
setUpLocationClientIfNeeded();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
mGoogleApiClient.connect();
Log.d(TAG,"onStartCommand: ");
PowerManager mgr = (PowerManager)getSystemService(Context.POWER_SERVICE);
/*
WakeLock is reference counted so we don't want to create multiple WakeLocks. So do a check before initializing and acquiring.
This will fix the "java.lang.Exception: WakeLock finalized while still held: MyWakeLock" error that you may find.
*/
if (this.mWakeLock == null) { //**Added this
this.mWakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
}
if (!this.mWakeLock.isHeld()) { //**Added this
this.mWakeLock.acquire();
}
if(!servicesAvailable || mGoogleApiClient.isConnected() || mInProgress)
return START_STICKY;
setUpLocationClientIfNeeded();
if(!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress)
{
//appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Started", Constants.LOG_FILE);
mInProgress = true;
mGoogleApiClient.connect();
}
return START_STICKY;
}
private void setUpLocationClientIfNeeded()
{
if(mGoogleApiClient == null)
googleApiClientBuild();
}
protected synchronized void googleApiClientBuild(){
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
#Override
public void onConnected(#Nullable Bundle bundle) {
mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(120000); // Update location every 60 seconds
Log.d(TAG,"in onConnected: ");
if ( ContextCompat.checkSelfPermission( this, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) {
ActivityCompat.requestPermissions( mActivity, new String[] { android.Manifest.permission.ACCESS_COARSE_LOCATION },
REQUEST_CODE_LOCATION );
}else {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
//Location mLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
//txtOutput.setText(mLocation.toString());
Log.d(TAG, "calling fusedLocationApi");
}
continueAddGeofences();
}
#Override
public void onConnectionSuspended(int i) {
// Turn off the request flag
mInProgress = false;
// Destroy the current location client
mGoogleApiClient = null;
Log.d(TAG, "onConnectionSuspended ");
}
#Override
public void onLocationChanged(Location location) {
mLocation = location;
Log.d(TAG,"in onLocationChanged: ");
Log.d(TAG, mLocation.toString());
// save new location to db
connectToDb(); // checking for status 1
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
mInProgress = false;
Log.d(TAG, "onConnectionFailed");
/*
* 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()) {
Log.d(TAG, "google play services has solution");
// If no resolution is available, display an error dialog
} else {
Log.d(TAG, "no solution for connection failure");
}
}
#Override
public void onDestroy() {
// Turn off the request flag
this.mInProgress = false;
if (this.servicesAvailable && this.mGoogleApiClient != null) {
this.mGoogleApiClient.unregisterConnectionCallbacks(this);
this.mGoogleApiClient.unregisterConnectionFailedListener(this);
this.mGoogleApiClient.disconnect();
// Destroy the current location client
this.mGoogleApiClient = null;
}
// Display the connection status
// Toast.makeText(this, DateFormat.getDateTimeInstance().format(new Date()) + ":
// Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show();
if (this.mWakeLock != null) {
this.mWakeLock.release();
this.mWakeLock = null;
}
super.onDestroy();
}
public void connectToDb(){
StringRequest stringRequest = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
#Override
public void onResponse(String s) {
Toast.makeText(getApplicationContext() , ""+s,Toast.LENGTH_LONG).show();
Log.d(TAG, ""+s);
try {
JSONObject jsonObject = new JSONObject(s);
JSONArray jsonArray = jsonObject.getJSONArray("result");
if(jsonArray.length()!=0){
Log.d(TAG,"array length not 0");
JSONObject personInDanger = jsonArray.getJSONObject(0);
setGeofence(Double.valueOf(personInDanger.getString("latitude")), Double.valueOf(personInDanger.getString("longitude")));
}
}catch (JSONException je) {
// do something with it
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
Toast.makeText(getApplicationContext(), ""+volleyError, Toast.LENGTH_LONG).show();
}
});
requestQueue.add(stringRequest);
}
/**
* Verify that Google Play services is available before making a request.
*
* #return true if Google Play services is available, otherwise false
*/
private boolean servicesConnected() {
// Check that Google Play services is available
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
// If Google Play services is available
if (ConnectionResult.SUCCESS == resultCode) {
// In debug mode, log the status
Log.d(GeofenceUtils.APPTAG, getString(R.string.play_services_available));
// Continue
return true;
// Google Play services was not available for some reason
} else {
// Display an error dialog
Toast.makeText(getApplicationContext(), "Google play services not available", Toast.LENGTH_SHORT).show();
//Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, (Activity) getApplicationContext(), 0);
//if (dialog != null) {
// ErrorDialogFragment errorFragment = new ErrorDialogFragment();
// errorFragment.setDialog(dialog);
// errorFragment.show(getSupportFragmentManager(), GeofenceUtils.APPTAG);
}
return false;
}
public void setGeofence(Double latitude , Double longitude){
Log.d(TAG, "in setGeofence");
if (!servicesConnected()) {
return;
}
SimpleGeofence newGeofence = new SimpleGeofence(
"1",
// Get latitude, longitude, and radius from the db
latitude,
longitude,
Float.valueOf("50.0"),
// Set the expiration time
Geofence.NEVER_EXPIRE,
// Detect both entry and exit transitions
Geofence.GEOFENCE_TRANSITION_ENTER
);
mCurrentGeofences.add(newGeofence.toGeofence());
// Start the request. Fail if there's already a request in progress
try {
// Try to add geofences
addGeofences(mCurrentGeofences);
} catch (UnsupportedOperationException e) {
// Notify user that previous request hasn't finished.
Toast.makeText(this, R.string.add_geofences_already_requested_error,
Toast.LENGTH_LONG).show();
}
}
public void addGeofences(ArrayList<Geofence> geofences) throws UnsupportedOperationException {
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();
}
}
private void requestConnection() {
getLocationClient().connect();
}
private GoogleApiClient getLocationClient() {
if (mGoogleApiClient == null) {
mGoogleApiClient = new GoogleApiClient.Builder(mActivity)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
return mGoogleApiClient;
}
private void continueAddGeofences() {
// Get a PendingIntent that Location Services issues when a geofence transition occurs
mGeofencePendingIntent = createRequestPendingIntent();
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(mActivity, "not connected", Toast.LENGTH_SHORT).show();
return;
}
try {
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
// The GeofenceRequest object.
mCurrentGeofences,
// A pending intent that that is reused when calling removeGeofences(). This
// pending intent is used to generate an intent when a matched geofence
// transition is observed.
mGeofencePendingIntent
).setResultCallback(this); // Result processed in onResult().
} catch (SecurityException securityException) {
// Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
logSecurityException(securityException);
}
}
private void logSecurityException(SecurityException securityException) {
Log.e(GeofenceUtils.APPTAG , "Invalid location permission. " +
"You need to use ACCESS_FINE_LOCATION with geofences", securityException);
}
public void onResult(Status status) {
Intent broadcastIntent = new Intent();
// Temp storage for messages
String msg;
if (status.isSuccess()) {
Toast.makeText(
mActivity, "Geofences added",
Toast.LENGTH_SHORT
).show();
msg = mActivity.getString(R.string.add_geofences_result_success, status);
// In debug mode, log the result
Log.d(GeofenceUtils.APPTAG, msg);
// Create an Intent to broadcast to the app
broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCES_ADDED)
.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.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 = mActivity.getString(
R.string.add_geofences_result_failure,
status.getStatusCode(), status.getStatusMessage());
// Log an error
Log.e(GeofenceUtils.APPTAG, msg);
}
}
private PendingIntent createRequestPendingIntent() {
// If the PendingIntent already exists
if (null != mGeofencePendingIntent) {
// Return the existing intent
return mGeofencePendingIntent;
// If no PendingIntent exists
} else {
Intent intent = new Intent("com.example.android.geofence.ACTION_RECEIVE_GEOFENCE");
return PendingIntent.getBroadcast(
mActivity,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}
}
My logs:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
at android.app.PendingIntent.getBroadcastAsUser(PendingIntent.java:506)
at android.app.PendingIntent.getBroadcast(PendingIntent.java:495)
at com.example.android.safetyalert.GPSPollingService.createRequestPendingIntent(GPSPollingService.java:449)
at com.example.android.safetyalert.GPSPollingService.continueAddGeofences(GPSPollingService.java:369)
at com.example.android.safetyalert.GPSPollingService.onConnected(GPSPollingService.java:163)
at com.google.android.gms.common.internal.zzl.zzm(Unknown Source)
at com.google.android.gms.internal.zzof.zzk(Unknown Source)
at com.google.android.gms.internal.zzod.zzsb(Unknown Source)
at com.google.android.gms.internal.zzod.onConnected(Unknown Source)
at com.google.android.gms.internal.zzoh.onConnected(Unknown Source)
at com.google.android.gms.internal.zznw.onConnected(Unknown Source)
at com.google.android.gms.common.internal.zzk$1.onConnected(Unknown Source)
at com.google.android.gms.common.internal.zzd$zzj.zztp(Unknown Source)
at com.google.android.gms.common.internal.zzd$zza.zzc(Unknown Source)
at com.google.android.gms.common.internal.zzd$zza.zzw(Unknown Source)
at com.google.android.gms.common.internal.zzd$zze.zztr(Unknown Source)
at com.google.android.gms.common.internal.zzd$zzd.handleMessage(Unknown Source)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5312)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:696)
I have tried everything I could think of.
I'm polling the GPS in order to provide accurate location to the OS to increase the accuracy of geofencing. I know it will drain my battery but for now that's not a problem.
Service already has Context. You shouldn't use Context from the Activity because if your Activity is killed mActivity will be null causing all sort of issues. Replace else block in createRequestPendingIntent with this
} else {
Intent intent = new Intent("com.example.android.geofence.ACTION_RECEIVE_GEOFENCE");
return PendingIntent.getBroadcast(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
I`d like to detect user user's activity in background when app is closed. There is an example of using ActivityRecognition from Google APIs for Android.
In the example the recognition is initiated by an Activity, and I made my service realization.
It works fine on most devices but not on devices with api 4.2.x.
public class RecognitionService extends Service implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
ResultCallback<Status> {
private static final String TAG = RecognitionService.class.getSimpleName();
public final static int DETECTION_INTERVAL_IN_MILLISECONDS = 60_000;
private GoogleApiClient googleApiClient;
public RecognitionService() {
super();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
initLocationClient();
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void initLocationClient() {
if (googleApiClient == null) {
googleApiClient = new GoogleApiClient
.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(ActivityRecognition.API)
.build();
}
if (!googleApiClient.isConnected() || !googleApiClient.isConnecting()) {
googleApiClient.connect();
}
}
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected to GoogleApiClient");
if (googleApiClient != null && googleApiClient.isConnected()) {
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(
googleApiClient,
DETECTION_INTERVAL_IN_MILLISECONDS,
getActivityDetectionPendingIntent()
).setResultCallback(this);
}
}
#Override
public void onConnectionSuspended(int i) {
Log.i(TAG, "Connection suspended");
googleApiClient.connect();
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + connectionResult.getErrorCode());
}
private PendingIntent getActivityDetectionPendingIntent() {
Intent intent = new Intent(this, DetectedActivitiesIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// requestActivityUpdates() and removeActivityUpdates().
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
#Override
public void onResult(Status status) {
Log.i(TAG, "onResult = " + status.getStatusMessage());
}
#Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
ActivityRecognition.ActivityRecognitionApi.removeActivityUpdates(
googleApiClient,
getActivityDetectionPendingIntent()
).setResultCallback(this);
}
}
Methods onConnected, onConnectionSuspended, onConnectionFailed are not called.
Do you have any ideas about this case?
Confirm that the device has the the correct version of GooglePlayServices installed using isGooglePlayServicesAvailable():
GoogleApiAvailability apiAvail = GoogleApiAvailability.getInstance();
int errorCode = apiAvail.isGooglePlayServicesAvailable(this);
if (errorCode == ConnectionResult.SUCCESS) {
// okay
} else {
// not available; start processing to resolve
}
Although unavailability of Play Services should be reported after a connection request to onConnectionFailed(), using isGooglePlayServicesAvailable() offers another means to check availability.
This SO question addresses version support for ActivityRecognition.
I'm trying to implement Geofence on my APP and the problem is that is not triggering when I'm on the place that I've marked.
The thing that I do is I set the Latitude & Longitude from a Place Picker.
The values that I'm getting are fine because I put it on google maps and the place is the correct one, because I've read many answers that people was setting bad values from lat/long.
I've two classes : GeofenceSingleton.class and GeofenceTransitionsIntentService.
The GeofenceSingleton.class looks like :
public class GeofenceSingleton implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<Status> {
private GoogleApiClient googleApiClient;
private static GeofenceSingleton _singleInstance;
private static Context appContext;
private static final String ERR_MSG = "Application Context is not set!! " +
"Please call GeofenceSngleton.init() with proper application context";
private PendingIntent mGeofencePendingIntent;
private static ArrayList<Geofence> mGeofenceList;
private GeofenceSingleton() {
if (appContext == null)
throw new IllegalStateException(ERR_MSG);
this.googleApiClient = new GoogleApiClient.Builder(this.appContext)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
this.googleApiClient.connect();
mGeofenceList = new ArrayList<Geofence>();
}
/**
* #param applicationContext
*/
public static void init(Context applicationContext) {
appContext = applicationContext;
}
public static GeofenceSingleton getInstance() {
if (_singleInstance == null)
synchronized (GeofenceSingleton.class) {
if (_singleInstance == null)
_singleInstance = new GeofenceSingleton();
}
return _singleInstance;
}
#Override
public void onConnected(Bundle bundle) {
}
#Override
public void onConnectionSuspended(int i) {
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
public void addGeofence(Location location, String uid) {
mGeofenceList.add(new Geofence.Builder()
.setRequestId(uid)
.setCircularRegion(
location.getLatitude(),
location.getLongitude(),
500
)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.setExpirationDuration(1000000)
.build());
}
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeofenceList);
return builder.build();
}
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(appContext, GeofenceSingleton.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
return PendingIntent.getService(appContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public void startGeofencing() {
if (!googleApiClient.isConnected()) {
Toast.makeText(appContext, "API client not connected", Toast.LENGTH_SHORT).show();
return;
}
LocationServices.GeofencingApi.addGeofences(
googleApiClient,
// The GeofenceRequest object.
getGeofencingRequest(),
// A pending intent that that is reused when calling removeGeofences(). This
// pending intent is used to generate an intent when a matched geofence
// transition is observed.
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
Toast.makeText(appContext,"Geofencing started", Toast.LENGTH_LONG).show();
}
public void removeGeofence(){
LocationServices.GeofencingApi.removeGeofences(
googleApiClient,
// This is the same pending intent that was used in addGeofences().
getGeofencePendingIntent()
);
}
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
// Update state and save in shared preferences.
Toast.makeText(
appContext,
"YO",
Toast.LENGTH_SHORT
).show();
} else {
Toast.makeText(
appContext,
"NOO",
Toast.LENGTH_SHORT
).show();
}
}
}
And GeofenceTransitionsIntentService
public class GeofenceTransitionsIntentService extends IntentService {
private static final String TAG = "Geofence-Service";
private Handler handler;
SharedPreferences sp;
SharedPreferences.Editor editor;
String EmailName, MessageEmail;
Context mContext;
Boolean EmailSent = false;
public GeofenceTransitionsIntentService() {
super(TAG);
}
#Override
public void onCreate() {
super.onCreate();
this.mContext = this;
sp = PreferenceManager.getDefaultSharedPreferences(this);
handler = new Handler();
}
#Override
protected void onHandleIntent(Intent intent) {
final GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
Log.e(TAG, "Error in geofencing event");
return;
}
// Get the transition type.
final int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == 1) {
handler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), "Entered", Toast.LENGTH_SHORT).show();
sendNotification();
}
});
Log.i(TAG, "Entered");
}
else {
// Log the error.
handler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), "Sorry you are out", Toast.LENGTH_SHORT).show();
}
});
Log.e(TAG, String.valueOf(geofenceTransition));
}
}
private void sendNotification() {
Toast.makeText(GeofenceTransitionsIntentService.this, "HEEEEEEEY I'M IN", Toast.LENGTH_SHORT).show();
}
On my MainActivity I've got this :
GeofenceSingleton.init(this); //If I don't call this the class doesn't work
this.geofenceSingleton = GeofenceSingleton.getInstance();
And then I build a Geofence as follows :
Location geofence = new Location("");
geofence.setLatitude(Double.parseDouble(latitude));
geofence.setLongitude(Double.parseDouble(longitude));
geofenceSingleton.addGeofence(geofence, GeofenceKey);
geofenceSingleton.startGeofencing();
NOTES
I've added <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> on my manifest.xml
I declared the service as <service android:name=".GeofenceTransitionsIntentService"/>
And I added this line on my build.gradle : compile 'com.google.android.gms:play-services:7.8.0'
It only shows Toast.makeText(appContext,"YO",Toast.LENGTH_SHORT).show(); that's because status is success, and also shows the Toast.makeText(appContext,"Geofencing started", Toast.LENGTH_LONG).show(); means that it's started.
QUESTION
Why my IntentService is never called?
I tested it putting a Lat/Long of my home and since I'm on it should be triggered, isn't it?
It will never work because what you are doing is
Intent intent = new Intent(appContext, GeofenceSingleton.class);
passing intent to GeofenceSingleton ?
What you need to do is
Intent intent = new Intent(appContext, GeofenceTransitionsIntentService.class);
If your Intent is already constructed with the proper class (see varunkr's answer), you also need to make sure the service is also included in your AndroidManifest.xml.
<application
...
<service android:name=".GeofencingTransitionIntentService" />
Make sure your service and broadcast receive have these property enabled in manifest.
android:enabled="true"
android:exported="true"
My GoogleApiClient connects successfully and my #Override public void onDataPoint(DataPoint dataPoint) {…} is called exactly once and then never again. This is within a Service that's started only after Google Fit is authorized from within the UI (that's another can of worms). The Service has another GoogleApiClient that successfully runs and is called at my specified interval.
Here's what I see from logcat:
mGoogleFitApiClient connected.
mGoogleFitApiClient listener registered.
mGoogleFitApiClient detected DataPoint: still (100.0% confidence)
It's then never called again and #Override public void onConnectionSuspended(int i) {…} is never called either.
Here's the relevant code from my Service:
private void buildFitnessClient() {
mGoogleFitApiClient = new GoogleApiClient.Builder(this)
.useDefaultAccount()
.addApi(Fitness.SENSORS_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ))
.addConnectionCallbacks(
new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "mGoogleFitApiClient connected.");
setupPhysicalActivityListener();
}
#Override
public void onConnectionSuspended(int i) {
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "mGoogleFitApiClient connection lost. Cause: network lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "mGoogleFitApiClient connection lost. Reason: service disconnected");
}
}
}
)
.addOnConnectionFailedListener(
new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "mGoogleFitApiClient connection failed. Cause: " + result.toString());
}
}
)
.build();
}
#Override
public void onDataPoint(DataPoint dataPoint) {
String activityName = "";
float confidence = 0;
for (Field field : dataPoint.getDataType().getFields()) {
if (field.getName().equals("activity")) {
activityName = dataPoint.getValue(field).asActivity();
} else if (field.getName().equals("confidence")) {
confidence = dataPoint.getValue(field).asFloat();
}
}
lastActivityName = activityName;
lastActivityConfidence = confidence;
Log.i(TAG, "mGoogleFitApiClient detected DataPoint: " + activityName + " (" + confidence + "% confidence)");
}
private void setupPhysicalActivityListener() {
SensorRequest sensorRequest = new SensorRequest.Builder()
.setDataType(DataType.TYPE_ACTIVITY_SAMPLE)
.setSamplingRate(15, TimeUnit.SECONDS)
.setFastestRate(5, TimeUnit.SECONDS)
.setAccuracyMode(SensorRequest.ACCURACY_MODE_LOW)
.build();
Fitness.SensorsApi.add(mGoogleFitApiClient, sensorRequest, this)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.i(TAG, "mGoogleFitApiClient listener registered.");
} else {
Log.i(TAG, "mGoogleFitApiClient listener not registered.");
}
}
});
}
The request data type is
setDataType(DataType.TYPE_ACTIVITY_SAMPLE)
Try
DataType.TYPE_STEP_COUNT_DELTA or DataType.TYPE_HEART_RATE_BPM
to see if you get the same result.(only called once)
Could it be because the screen becomes locked?
Does anyone know of an example of using the LocationServices.GeofencingApi?
All the android geofencing examples I find are using the deprecated LocationClient class.
From what I can see, the LocationServices class is the one to use, but there doesn't seem to be any working examples on how to use it.
The closest I've found is this post highlighting location update requests
UPDATE: The closest answer I've found is this git example project - but it still uses the deprecated LocationClient to get triggered fences.
I just migrated my code to the new API. Here is a working example:
A working project on GitHub based on this answer: https://github.com/androidfu/GeofenceExample
This helper class registers the geofences using the API. I use a callback interface to communicate with the calling activity/fragment. You can build a callback that fits your needs.
public class GeofencingRegisterer implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private Context mContext;
private GoogleApiClient mGoogleApiClient;
private List<Geofence> geofencesToAdd;
private PendingIntent mGeofencePendingIntent;
private GeofencingRegistererCallbacks mCallback;
public final String TAG = this.getClass().getName();
public GeofencingRegisterer(Context context){
mContext =context;
}
public void setGeofencingCallback(GeofencingRegistererCallbacks callback){
mCallback = callback;
}
public void registerGeofences(List<Geofence> geofences){
geofencesToAdd = geofences;
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
}
#Override
public void onConnected(Bundle bundle) {
if(mCallback != null){
mCallback.onApiClientConnected();
}
mGeofencePendingIntent = createRequestPendingIntent();
PendingResult<Status> result = LocationServices.GeofencingApi.addGeofences(mGoogleApiClient, geofencesToAdd, mGeofencePendingIntent);
result.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
// Successfully registered
if(mCallback != null){
mCallback.onGeofencesRegisteredSuccessful();
}
} else if (status.hasResolution()) {
// Google provides a way to fix the issue
/*
status.startResolutionForResult(
mContext, // your current activity used to receive the result
RESULT_CODE); // the result code you'll look for in your
// onActivityResult method to retry registering
*/
} else {
// No recovery. Weep softly or inform the user.
Log.e(TAG, "Registering failed: " + status.getStatusMessage());
}
}
});
}
#Override
public void onConnectionSuspended(int i) {
if(mCallback != null){
mCallback.onApiClientSuspended();
}
Log.e(TAG, "onConnectionSuspended: " + i);
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if(mCallback != null){
mCallback.onApiClientConnectionFailed(connectionResult);
}
Log.e(TAG, "onConnectionFailed: " + connectionResult.getErrorCode());
}
/**
* Returns the current PendingIntent to the caller.
*
* #return The PendingIntent used to create the current set of geofences
*/
public PendingIntent getRequestPendingIntent() {
return createRequestPendingIntent();
}
/**
* 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 (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
} else {
Intent intent = new Intent(mContext, GeofencingReceiver.class);
return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}
}
This class is the base class for your geofence transition receiver.
public abstract class ReceiveGeofenceTransitionIntentService extends IntentService {
/**
* Sets an identifier for this class' background thread
*/
public ReceiveGeofenceTransitionIntentService() {
super("ReceiveGeofenceTransitionIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent event = GeofencingEvent.fromIntent(intent);
if(event != null){
if(event.hasError()){
onError(event.getErrorCode());
} else {
int transition = event.getGeofenceTransition();
if(transition == Geofence.GEOFENCE_TRANSITION_ENTER || transition == Geofence.GEOFENCE_TRANSITION_DWELL || transition == Geofence.GEOFENCE_TRANSITION_EXIT){
String[] geofenceIds = new String[event.getTriggeringGeofences().size()];
for (int index = 0; index < event.getTriggeringGeofences().size(); index++) {
geofenceIds[index] = event.getTriggeringGeofences().get(index).getRequestId();
}
if (transition == Geofence.GEOFENCE_TRANSITION_ENTER || transition == Geofence.GEOFENCE_TRANSITION_DWELL) {
onEnteredGeofences(geofenceIds);
} else if (transition == Geofence.GEOFENCE_TRANSITION_EXIT) {
onExitedGeofences(geofenceIds);
}
}
}
}
}
protected abstract void onEnteredGeofences(String[] geofenceIds);
protected abstract void onExitedGeofences(String[] geofenceIds);
protected abstract void onError(int errorCode);
}
This class implements the abstract class and does all the handling of geofence transitions
public class GeofencingReceiver extends ReceiveGeofenceTransitionIntentService {
#Override
protected void onEnteredGeofences(String[] geofenceIds) {
Log.d(GeofencingReceiver.class.getName(), "onEnter");
}
#Override
protected void onExitedGeofences(String[] geofenceIds) {
Log.d(GeofencingReceiver.class.getName(), "onExit");
}
#Override
protected void onError(int errorCode) {
Log.e(GeofencingReceiver.class.getName(), "Error: " + i);
}
}
And in your manifest add:
<service
android:name="**xxxxxxx**.GeofencingReceiver"
android:exported="true"
android:label="#string/app_name" >
</service>
Callback Interface
public interface GeofencingRegistererCallbacks {
public void onApiClientConnected();
public void onApiClientSuspended();
public void onApiClientConnectionFailed(ConnectionResult connectionResult);
public void onGeofencesRegisteredSuccessful();
}