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();
}
Related
I have registered my beacon on the Google Beacon Platform and download the "Beacon Tools" android app to scan the beacon. Below I have attached its screenshot.
Now I'm developing an android app for that beacon and using this app I can get attachments that I set for the beacon. I 'am using Nearby Messages API for this. Now I want to get UUID and distance between user & beacon. I 'm new to these things and read many stackoverflow questions and answers. but those didn't solve my problem
Below I have mentioned my classes.
MainActivity.java
public class MainActivity extends AppCompatActivity implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = MainActivity.class.getSimpleName();
private static final int PERMISSIONS_REQUEST_CODE = 1111;
private static final String KEY_SUBSCRIBED = "subscribed";
/**
* The entry point to Google Play Services.
*/
private GoogleApiClient mGoogleApiClient;
/**
* The container {#link android.view.ViewGroup} for the minimal UI associated with this sample.
*/
private RelativeLayout mContainer;
/**
* Tracks subscription state. Set to true when a call to
* {#link Messages#subscribe(GoogleApiClient, MessageListener)} succeeds.
*/
private boolean mSubscribed = false;
/**
* Adapter for working with messages from nearby beacons.
*/
private ArrayAdapter<String> mNearbyMessagesArrayAdapter;
/**
* Backing data structure for {#code mNearbyMessagesArrayAdapter}.
*/
private List<String> mNearbyMessagesList = new ArrayList<>();
FirebaseDatabase db = FirebaseDatabase.getInstance();
DatabaseReference databaseReference;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
//setSupportActionBar(toolbar);
if (savedInstanceState != null) {
mSubscribed = savedInstanceState.getBoolean(KEY_SUBSCRIBED, false);
}
mContainer = (RelativeLayout) findViewById(R.id.main_activity_container);
if (!havePermissions()) {
Log.i(TAG, "Requesting permissions needed for this app.");
requestPermissions();
}
final List<String> cachedMessages = Utils.getCachedMessages(this);
if (cachedMessages != null) {
mNearbyMessagesList.addAll(cachedMessages);
}
final ListView nearbyMessagesListView = (ListView) findViewById(
R.id.nearby_messages_list_view);
mNearbyMessagesArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
mNearbyMessagesList);
if (nearbyMessagesListView != null) {
nearbyMessagesListView.setAdapter(mNearbyMessagesArrayAdapter);
}
}
#Override
protected void onResume() {
super.onResume();
getSharedPreferences(getApplicationContext().getPackageName(), Context.MODE_PRIVATE)
.registerOnSharedPreferenceChangeListener(this);
if (havePermissions()) {
buildGoogleApiClient();
}
}
#TargetApi(Build.VERSION_CODES.M)
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode != PERMISSIONS_REQUEST_CODE) {
return;
}
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
if (shouldShowRequestPermissionRationale(permission)) {
Log.i(TAG, "Permission denied without 'NEVER ASK AGAIN': " + permission);
showRequestPermissionsSnackbar();
} else {
Log.i(TAG, "Permission denied with 'NEVER ASK AGAIN': " + permission);
showLinkToSettingsSnackbar();
}
} else {
Log.i(TAG, "Permission granted, building GoogleApiClient");
buildGoogleApiClient();
}
}
}
private synchronized void buildGoogleApiClient() {
if (mGoogleApiClient == null) {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Nearby.MESSAGES_API, new MessagesOptions.Builder()
.setPermissions(NearbyPermissions.BLE).build())
.addConnectionCallbacks(this)
.enableAutoManage(this, this)
.build();
}
}
#Override
protected void onPause() {
getSharedPreferences(getApplicationContext().getPackageName(), Context.MODE_PRIVATE)
.unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
if (mContainer != null) {
Snackbar.make(mContainer, "Exception while connecting to Google Play services: " +
connectionResult.getErrorMessage(),
Snackbar.LENGTH_INDEFINITE).show();
}
}
#Override
public void onConnectionSuspended(int i) {
Log.w(TAG, "Connection suspended. Error code: " + i);
}
#Override
public void onConnected(#Nullable Bundle bundle) {
Log.i(TAG, "GoogleApiClient connected");
subscribe();
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (TextUtils.equals(key, Utils.KEY_CACHED_MESSAGES)) {
mNearbyMessagesList.clear();
mNearbyMessagesList.addAll(Utils.getCachedMessages(this));
mNearbyMessagesArrayAdapter.notifyDataSetChanged();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_SUBSCRIBED, mSubscribed);
}
private boolean havePermissions() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
}
private void requestPermissions() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_CODE);
}
/**
* Calls {#link Messages#subscribe(GoogleApiClient, MessageListener, SubscribeOptions)},
* using a {#link Strategy} for BLE scanning. Attaches a {#link ResultCallback} to monitor
* whether the call to {#code subscribe()} succeeded or failed.
*/
private void subscribe() {
// In this sample, we subscribe when the activity is launched, but not on device orientation
// change.
if (mSubscribed) {
Log.i(TAG, "Already subscribed.");
return;
}
SubscribeOptions options = new SubscribeOptions.Builder()
.setStrategy(Strategy.BLE_ONLY)
.build();
Nearby.Messages.subscribe(mGoogleApiClient, getPendingIntent(), options)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Subscribed successfully.");
startService(getBackgroundSubscribeServiceIntent());
} else {
Log.e(TAG, "Operation failed. Error: " +
NearbyMessagesStatusCodes.getStatusCodeString(
status.getStatusCode()));
}
}
});
}
private PendingIntent getPendingIntent() {
return PendingIntent.getService(this, 0,
getBackgroundSubscribeServiceIntent(), PendingIntent.FLAG_UPDATE_CURRENT);
}
private Intent getBackgroundSubscribeServiceIntent() {
return new Intent(this, BackgroundSubscribeIntentService.class);
}
/**
* Displays {#link Snackbar} instructing user to visit Settings to grant permissions required by
* this application.
*/
private void showLinkToSettingsSnackbar() {
if (mContainer == null) {
return;
}
Snackbar.make(mContainer,
R.string.permission_denied_explanation,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.settings, new View.OnClickListener() {
#Override
public void onClick(View view) {
// Build intent that displays the App settings screen.
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package",
BuildConfig.APPLICATION_ID, null);
intent.setData(uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}).show();
}
/**
* Displays {#link Snackbar} with button for the user to re-initiate the permission workflow.
*/
private void showRequestPermissionsSnackbar() {
if (mContainer == null) {
return;
}
Snackbar.make(mContainer, R.string.permission_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, new View.OnClickListener() {
#Override
public void onClick(View view) {
// Request permission.
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSIONS_REQUEST_CODE);
}
}).show();
}
}
BackgroundSubscribeIntentService.java
public class BackgroundSubscribeIntentService extends IntentService {
private static final String TAG = "BackSubIntentService";
private static final int MESSAGES_NOTIFICATION_ID = 1;
private static final int NUM_MESSAGES_IN_NOTIFICATION = 5;
public BackgroundSubscribeIntentService() {
super("BackgroundSubscribeIntentService");
}
#Override
public void onCreate() {
super.onCreate();
updateNotification();
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
Nearby.Messages.handleIntent(intent, new MessageListener() {
#Override
public void onFound(Message message) {
Utils.saveFoundMessage(getApplicationContext(), message);
updateNotification();
}
#Override
public void onLost(Message message) {
Utils.removeLostMessage(getApplicationContext(), message);
updateNotification();
}
});
}
}
private void updateNotification() {
List<String> messages = Utils.getCachedMessages(getApplicationContext());
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent launchIntent = new Intent(getApplicationContext(), MainActivity.class);
launchIntent.setAction(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
String contentTitle = getContentTitle(messages);
String contentText = getContentText(messages);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.star_on)
.setContentTitle(contentTitle)
.setContentText(contentText)
.setStyle(new NotificationCompat.BigTextStyle().bigText(contentText))
.setOngoing(true)
.setContentIntent(pi);
notificationManager.notify(MESSAGES_NOTIFICATION_ID, notificationBuilder.build());
}
private String getContentTitle(List<String> messages) {
switch (messages.size()) {
case 0:
return getResources().getString(R.string.scanning);
case 1:
return getResources().getString(R.string.one_message);
default:
return getResources().getString(R.string.many_messages, messages.size());
}
}
private String getContentText(List<String> messages) {
String newline = System.getProperty("line.separator");
if (messages.size() < NUM_MESSAGES_IN_NOTIFICATION) {
return TextUtils.join(newline, messages);
}
return TextUtils.join(newline, messages.subList(0, NUM_MESSAGES_IN_NOTIFICATION)) +
newline + "…";
}
}
Utils.java
public final class Utils {
static final String KEY_CACHED_MESSAGES = "cached-messages";
/**
* Fetches message strings stored in {#link SharedPreferences}.
*
* #param context The context.
* #return A list (possibly empty) containing message strings.
*/
static List<String> getCachedMessages(Context context) {
SharedPreferences sharedPrefs = getSharedPreferences(context);
String cachedMessagesJson = sharedPrefs.getString(KEY_CACHED_MESSAGES, "");
if (TextUtils.isEmpty(cachedMessagesJson)) {
return Collections.emptyList();
} else {
Type type = new TypeToken<List<String>>() {}.getType();
return new Gson().fromJson(cachedMessagesJson, type);
}
}
/**
* Saves a message string to {#link SharedPreferences}.
*
* #param context The context.
* #param message The Message whose payload (as string) is saved to SharedPreferences.
*/
static void saveFoundMessage(Context context, Message message) {
ArrayList<String> cachedMessages = new ArrayList<>(getCachedMessages(context));
Set<String> cachedMessagesSet = new HashSet<>(cachedMessages);
String messageString = new String(message.getContent());
if (!cachedMessagesSet.contains(messageString)) {
cachedMessages.add(0, new String(message.getContent()));
getSharedPreferences(context)
.edit()
.putString(KEY_CACHED_MESSAGES, new Gson().toJson(cachedMessages))
.apply();
}
}
/**
* Removes a message string from {#link SharedPreferences}.
* #param context The context.
* #param message The Message whose payload (as string) is removed from SharedPreferences.
*/
static void removeLostMessage(Context context, Message message) {
ArrayList<String> cachedMessages = new ArrayList<>(getCachedMessages(context));
cachedMessages.remove(new String(message.getContent()));
getSharedPreferences(context)
.edit()
.putString(KEY_CACHED_MESSAGES, new Gson().toJson(cachedMessages))
.apply();
}
/**
* Gets the SharedPReferences object that is used for persisting data in this application.
*
* #param context The context.
* #return The single {#link SharedPreferences} instance that can be used to retrieve and
modify values.
*/
static SharedPreferences getSharedPreferences(Context context) {
return context.getSharedPreferences(
context.getApplicationContext().getPackageName(),
Context.MODE_PRIVATE);
}
}
How can I get UUID and distance of the beacon and where I can apply the code. I have no prior experience of beacons.
The code shown in the question sets up a background subscription to BLE beacon Nearby messages that are delivered to a service by a PendingIntent: Nearby.Messages.subscribe(mGoogleApiClient, getPendingIntent(), options).setResultCallback(...);
Unfortunately, Google Nearby does not support delivering signal strength or distance estimates to background subscriptions. You can only do this with foreground subscriptions:
RSSI and Distance Callbacks
In addition to found and lost callbacks, a foreground subscription can update your MessageListener when Nearby has new information about the BLE signal associated with a message.
Note:
These extra callbacks are currently only delivered for BLE beacon messages (both attachments and beacon IDs).
These extra callbacks are not delivered to background (PendingIntent) subscriptions.
See https://developers.google.com/nearby/messages/android/advanced
If you want to get signal strength and distance estimates, with Google Nearby you must rewrite your code to use a foreground only subscription. The results will only be delivered if your app is in the foreground.
MessageListener messageListener = new MessageListener() {
#Override
public void onFound(final Message message) {
Log.i(TAG, "Found message: " + message);
}
#Override
public void onBleSignalChanged(final Message message, final BleSignal bleSignal) {
Log.i(TAG, "Message: " + message + " has new BLE signal information: " + bleSignal);
}
#Override
public void onDistanceChanged(final Message message, final Distance distance) {
Log.i(TAG, "Distance changed, message: " + message + ", new distance: " + distance);
}
#Override
public void onLost(final Message message) {
Log.i(TAG, "Lost message: " + message);
}
};
Nearby.getMessagesClient(this).subscribe(messageListener, options);
The link above shows a full example of how to do that.
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.
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"
Geofence does not get any transition updates, despite setting location 100 miles away,
does not trigger anything even GPS is on.
public class GeofenceManager extends IntentService implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, LocationClient.OnAddGeofencesResultListener, LocationClient.OnRemoveGeofencesResultListener {
private Context context;
private LocationClient locClient;
private PendingIntent penIntent;
private ArrayList<Geofence> geofenceList = new ArrayList<Geofence>();
private double dLongitude, dLatitude;
private boolean bGeofenceEnable = false;
private BroadcastReceiver receiverGeofence;
/* ===========================================================================================================================
* CONSTRUCTOR -- Checks if Google play service are available
*----------------------------------------------------------------------------------------------------------------------------*/
GeofenceManager(Context context){
super("ReceiveTransitionsIntentService");
this.context = context;
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
if (ConnectionResult.SUCCESS == resultCode) {
bGeofenceEnable = true;
} else {
Log.d(GPSManager.LOG_TAG,"GEO_FENCE: ERROR google play services not available");
}
}
/* ===========================================================================================================================
* METHOD -- using hard coded values for test purpose
*----------------------------------------------------------------------------------------------------------------------------*/
public void setGeofence( double latitude, double longitude){
if(bGeofenceEnable){
dLatitude = latitude = 51.515399;
dLongitude = longitude = -0.144313;
locClient.connect();
}
}
private PendingIntent createRequestPendingIntent() {
if (null != penIntent) {
return penIntent;
} else {
Intent intent = new Intent(context, GeofenceManager.class); // Create an Intent pointing to the IntentService
return PendingIntent.getService( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}
#Override public void onConnected(Bundle bundle) {
Geofence geofence = new Geofence.Builder()
.setRequestId("123")
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_EXIT|Geofence.GEOFENCE_TRANSITION_ENTER ) // exit geo fence listener
.setCircularRegion(dLatitude, dLongitude, 20) // GPS coordinates
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build();
geofenceList.add(geofence);
penIntent = createRequestPendingIntent();
locClient.addGeofences(geofenceList, penIntent, this);
}
#Override public void onDisconnected() { }
#Override public void onConnectionFailed(ConnectionResult connectionResult) { }
#Override public void onRemoveGeofencesByPendingIntentResult(int i, PendingIntent pendingIntent) { }
#Override public void onAddGeofencesResult(int i, String[] strings) {
if(i == LocationStatusCodes.SUCCESS){
Log.d(GPSManager.LOG_TAG, GPSManager.getTime()+" GEO_FENCE: added");
} else {
Log.d(GPSManager.LOG_TAG, GPSManager.getTime()+" GEO_FENCE: add ERROR "+i);
}
// locClient.disconnect();
}
#Override public void onRemoveGeofencesByRequestIdsResult(int i, String[] strings) {
if(i == LocationStatusCodes.SUCCESS)
Log.d(GPSManager.LOG_TAG, GPSManager.getTime()+" GEO_FENCE: old Removed");
}
#Override protected void onHandleIntent(Intent intent) {
int transition = LocationClient.getGeofenceTransition(intent);
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER) || (transition == Geofence.GEOFENCE_TRANSITION_EXIT)){
Log.d(GPSManager.LOG_TAG,"GEO_FENCE Transition detected");
Toast.makeText(context, "Geo fence movement detected", Toast.LENGTH_LONG).show();
ToneGenerator toneG = new ToneGenerator(AudioManager.STREAM_ALARM, 100);
toneG.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, 1000); // 200 is duration in ms
} else {
Log.d(GPSManager.LOG_TAG,"GEO_FENCE Transition UPDATE");
}
}
}
and in Manifest file I have declared:
<service android:name="com.Utilities.GeofenceManager" android:exported="true" />
also have tried:
<service android:name="com.Utilities.GeofenceManager" android:exported="false" />
<service android:name=".GeofenceManager" android:exported="false" />
Geofence is added as onAddGeofencesResult() method is called with success
Manage to fix the problem, by creating a new class for Geofence transition Intent,
and removed the IntentService code from GeofenceManager class
public class GeofenceIntentService extends IntentService {
public GeofenceIntentService() {
super("GeofenceIntentService");
}
#Override protected void onHandleIntent(Intent intent) {
int transition = LocationClient.getGeofenceTransition(intent);
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER) || (transition == Geofence.GEOFENCE_TRANSITION_EXIT)){
Log.d(GPSManager.LOG_TAG, "GEO_FENCE "+((transition == Geofence.GEOFENCE_TRANSITION_ENTER) ? "ENTER " : "EXIT") +" Transition detected");
Toast.makeText(this, "Geo fence movement detected", Toast.LENGTH_LONG).show();
ToneGenerator toneG = new ToneGenerator(AudioManager.STREAM_ALARM, 80);
toneG.startTone(ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE, 1000); // 200 is duration in ms
} else {
Log.d(GPSManager.LOG_TAG,"GEO_FENCE Transition UPDATE");
}
}
}