I have made two geofences by adding the coordinates from a sqlite table. It does create the geofences on the map and transitions also do occur.
My problem is that all the transitions trigger the same broadcast, this means no matter what two geofences I enter, it shows the same notification.
How do I get location specific messages?
I have used this line of code to get the triggered fence's ID and this works properly, but it also gives unwanted things such as the longitude, latitude and radius. How do I extract only the ID from this?
List<Geofence> ab = LocationClient.getTriggeringGeofences(intent);
This is the relevant part of the code.
#Override
public void onConnected(Bundle arg0) {
mClient.requestLocationUpdates(mRequest, this);
SQLiteDatabase db = database.getWritableDatabase();
String[] columns = {PromoDatabase.LID,PromoDatabase.lRestuarantID,PromoDatabase.lBranchID,PromoDatabase.Latitude,PromoDatabase.Longitude};
Cursor cursor = db.query(PromoDatabase.LOCATION_TABLE, columns, null, null, null, null, null);
String RestuarantName = null;
while(cursor.moveToNext()) //create the two geofences with with help of sqlite table
{
String LocationID = cursor.getString(0);
String RestuarantID = cursor.getString(1);
double latitude = cursor.getDouble(3);
double longitude = cursor.getDouble(4);
RestuarantName = getData(RestuarantID); //get the restuarant name by giving the RestuarantID
float radius = 800;
// Build a Geofence
Geofence fence = new Geofence.Builder()
.setRequestId(LocationID)
.setCircularRegion(latitude, longitude, radius)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build();
mList.add(fence);
googleMap.addMarker( new MarkerOptions()
.position( new LatLng(latitude, longitude) )
.title(RestuarantName+": Fence " + LocationID)
.snippet("Radius: " + radius) ).showInfoWindow();
circleOptions = new CircleOptions()
.center( new LatLng(latitude, longitude) )
.radius( radius )
.fillColor(0x40ff0000)
.strokeColor(Color.TRANSPARENT)
.strokeWidth(2);
googleMap.addCircle(circleOptions);
}
// Method 2: Using Broadcast
Intent intent = new Intent();
intent.setAction(GeofenceEventReceiver.GEOFENCE_EVENTS); // Specify the action, a.k.a. receivers
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.putExtra("Location", "KFC");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Send out the Geofence request
mClient.addGeofences(mList, pendingIntent, this);
}
// Broadcast receiver used to receive broadcast sent from the GeofenceIntentService
public class GeofenceEventReceiver extends BroadcastReceiver {
public static final String GEOFENCE_EVENTS = "com.Drogo.proto.GeofenceEvents";
#Override
public void onReceive(Context context, Intent intent) {
String locationInfo = "Arraived at " + intent.getStringExtra("Location");
List<Geofence> BreachedGeofence = LocationClient.getTriggeringGeofences(intent);
Toast.makeText(context, locationInfo + BreachedGeofence, Toast.LENGTH_LONG).show();
notifyMe(context,intent, locationInfo); //this will basically give me a notification through the notification manager.
}
Use
List <Geofence> triggerList = getTriggeringGeofences(intent);
String[] triggerIds = new String[geofenceList.size()];
for (int i = 0; i < triggerIds.length; i++) {
triggerIds[i] = triggerList.get(i).getRequestId();
}
taken from the Android documentation here.
Did you want something different from that?
Related
I am building an app that should remind the user about upcoming events that they set (basically a reminder). The issue i'm running into is pushing notifications to the user's phone (only on API 26+) when the app has not been utilized for a while (about 15 minutes or more); notifications are not displayed at all.
I read up on this and realized that App Standby and Doze mode may prevent my app from pushing such notifications; my notifications are received as intended by the user on phones running API 25 and below. To solve this I tried using AlarmManager.setExactAndAllowWhileIdle() but the issue persists.
class TaskNotifications {
private AlarmManager alarmManager;
private Context c;
TaskNotifications(Context context) {
this.c = context;
this.alarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
}
void setReminder(Context context, Task task) {
if (VERSION.SDK_INT < Build.VERSION_CODES.O) {
long reminderMilliseconds = task.getReminderMilliseconds();
if (reminderMilliseconds > Calendar.getInstance().getTimeInMillis() && !task.isDone()) {
Intent intent = new Intent(context, NotificationReceiver.class);
intent.putExtra("ID", task.getID());
intent.putExtra("TITLE", task.getTitle());
intent.putExtra("DETAILS", task.getDetails());
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, task.getID(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
if (VERSION.SDK_INT >= 23) {
this.alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, reminderMilliseconds, pendingIntent);
} else
this.alarmManager.setExact(AlarmManager.RTC_WAKEUP, reminderMilliseconds, pendingIntent);
}
}
}
void cancelReminder(Task task) {
if (VERSION.SDK_INT < Build.VERSION_CODES.O) {
this.alarmManager.cancel(PendingIntent.getBroadcast(this.c, task.getID(),
new Intent(this.c, NotificationReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT));
}
}
}
public class NotificationReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Intent startIntent = new Intent(context, NotificationJobIntentService.class);
startIntent.putExtra("ID", intent.getIntExtra("ID", -1));
startIntent.putExtra("TITLE", intent.getStringExtra("TITLE"));
startIntent.putExtra("DETAILS", intent.getStringExtra("DETAILS"));
JobIntentService.enqueueWork(context, NotificationJobIntentService.class, intent.getIntExtra("ID", -1), startIntent);
}
}
public class NotificationJobIntentService extends JobIntentService {
private String CHANNEL_ID = getResources().getString(R.string.channel_name);
protected void onHandleWork(#NonNull Intent intent) {
createNotificationChannel(NotificationJobIntentService.this);
int NOTIFICATION_ID = intent.getIntExtra("ID", -1);
String GROUP = "NOTIFICATION_GROUP";
String title = intent.getStringExtra("TITLE");
if (title.isEmpty())
title = getResources().getString(R.string.no_title);
String details = intent.getStringExtra("DETAILS");
if (details.isEmpty())
details = getResources().getString(R.string.no_details);
Intent openAppIntent = new Intent(NotificationJobIntentService.this, MainActivity.class);
TaskStackBuilder create = TaskStackBuilder.create(this);
create.addNextIntentWithParentStack(openAppIntent);
NotificationCompat.Builder builder = new NotificationCompat.Builder(NotificationJobIntentService.this, this.CHANNEL_ID)
.setContentTitle(title)
.setContentText(details)
.setSmallIcon(R.drawable.baseline_alarm_black_18)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setContentIntent(create.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT))
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setGroup(GROUP)
.setAutoCancel(true)
.setColor(Color.argb(100, 0, 87, 75))
.setVibrate(new long[]{1000, 1000})
.setLights(Color.GREEN, PathInterpolatorCompat.MAX_NUM_POINTS, PathInterpolatorCompat.MAX_NUM_POINTS)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM));
NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, builder.build());
}
private void createNotificationChannel(Context context) {
if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence string = context.getString(R.string.channel_name);
String description = context.getString(R.string.channel_description);
NotificationChannel notificationChannel = new NotificationChannel(this.CHANNEL_ID, string, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setDescription(description);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.GREEN);
notificationChannel.enableVibration(true);
notificationChannel.setVibrationPattern(new long[]{1000, 1000});
(context.getSystemService(NotificationManager.class)).createNotificationChannel(notificationChannel);
}
}
}
Is there a reliable way for me to send exact/somewhat exact notifications to my users' phones running API 26+? Or is there an error in my code that I did not notice?
I was unable to make the notification system work on API 26+ devices, however, I used the Android Calendar Provider Reminders to add the event to the users calendar and then set a reminder through the default calendar...not what I originally wanted, but it is a band-aid solution.
If anybody can still solve the problem as intended, please let me know.
Code below:
if (task.getEventID() > 0) {
//Remove existing events for this task
ContentResolver cr = c.getContentResolver();
int iNumRowsDeleted;
Uri eventUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, task.getEventID());
iNumRowsDeleted = cr.delete(eventUri, null, null);
Log.i("removeEvent()", "Deleted " + iNumRowsDeleted + " calendar entry.");
}
try {
//Add an event
ContentResolver cr = context.getContentResolver();
ContentValues values = new ContentValues();
values.put(CalendarContract.Events.DTSTART, task.getCal().getTimeInMillis());
values.put(CalendarContract.Events.DTEND, task.getCal().getTimeInMillis()+60*60*1000);//Each task a duration of 60 minutes
values.put(CalendarContract.Events.TITLE, task.getTitle() + " - " + task.getDetails());
values.put(CalendarContract.Events.CALENDAR_ID, getPrimaryCalendar());
values.put(CalendarContract.Events.EVENT_TIMEZONE, Calendar.getInstance().getTimeZone().getID());
Uri uri = cr.insert(CalendarContract.Events.CONTENT_URI, values);
// Save the eventId into the Task object for possible future delete.
task.setEventID(Integer.parseInt(uri.getLastPathSegment()));
Log.i("addEvent()","The event id is " + task.getEventID());
// Add a reminder
ContentValues valuesR = new ContentValues();
valuesR.put(CalendarContract.Reminders.MINUTES, (task.getCal().getTimeInMillis() - reminderMilliseconds)/(1000*60));
valuesR.put(CalendarContract.Reminders.EVENT_ID, task.getEventID());
valuesR.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT); /*The alarm method, as set on the server. METHOD_DEFAULT, METHOD_ALERT, METHOD_EMAIL, METHOD_SMS and METHOD_ALARM are possible values; the device will only process METHOD_DEFAULT and METHOD_ALERT reminders (the other types are simply stored so we can send the same reminder info back to the server when we make changes).*/
Uri uriR = cr.insert(CalendarContract.Reminders.CONTENT_URI, valuesR);
Cursor c = CalendarContract.Reminders.query(cr, task.getEventID(), new String[]{CalendarContract.Reminders.MINUTES});
if (c.moveToFirst()) {
Log.i("setReminder()",task.toString());
Log.i("setReminder()","calendar has reminder at " + c.getInt(c.getColumnIndex(CalendarContract.Reminders.MINUTES)));
}
c.close();
} catch (Exception e) {
e.printStackTrace();
}
My app uses geofences to notify the user when the enter a location, a notification gets displayed when the user enters the location.
what im trying to to is get the triggering geofences convert them to a string, then send them to another activity so that they can be displayed in a list view.
Ive watched tutorials online on how to makes list views and how to send data using intents but what im trying to do just doesn't seem to work.
EDIT
I'm starting the list view PageActivity using a intent with startActivity from another class the MainActivity.
Goals
If the user clicks on the notification from anywhere especially from outside the application it will open the app with the list view being filled with the geofence details. If the user opens the app just opens the app the from the launcher, then the same to happen. The list to be filled with any geofences that were triggered.
GeofenceTransitionsIntentService Class:
public class GeofenceTransitionsIntentService extends IntentService {
protected static final String TAG = "GeofenceTransitionsIS";
/**
* This constructor is required, and calls the super IntentService(String)
* constructor with the name for a worker thread.
*/
public GeofenceTransitionsIntentService() {
// Use the TAG to name the worker thread.
super(TAG);
}
#Override
public void onCreate() {
super.onCreate();
}
/**
* Handles incoming intents.
* #param intent sent by Location Services. This Intent is provided to Location
* Services (inside a PendingIntent) when addGeofences() is called.
*/
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()){
String errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.getErrorCode());
Log.e(TAG, errorMessage);
return;
}
// Get the Transistion Type
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transisition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
// Get the geofences that were triggered. A single event can trigger multiple geofences.
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Get the transistion details as a String.
String geofenceTransitionDetails = getGeofenceTransitionDetails(
this,
geofenceTransition,
triggeringGeofences
);
headsUpSmsNotifaction(geofenceTransitionDetails);
Intent i = new Intent(this, listPageActivty.class);
i.putExtra("intentKey",geofenceTransitionDetails);
Log.i(TAG, geofenceTransitionDetails);
} else{
// Log the error
Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition));
}
}
/**
* Gets transition details and returns them as a formatted string.
*
* #param context The app context.
* #param geofenceTransition The ID of the geofence transition.
* #param triggeringGeofences The geofence(s) triggered.
* #return The transition details formatted as String.
*/
private String getGeofenceTransitionDetails(Context context, int geofenceTransition, List<Geofence> triggeringGeofences) {
String geofenceTransitionString = getTransitionString(geofenceTransition);
// Get the Ids of each geofence that was triggered.
ArrayList triggeringGeofencesIdsList = new ArrayList();
for (Geofence geofence : triggeringGeofences) {
triggeringGeofencesIdsList.add(geofence.getRequestId());
}
String triggeringGeofencesIdsString = TextUtils.join(", ", triggeringGeofencesIdsList);
return geofenceTransitionString + ": " + triggeringGeofencesIdsString;
}
/**
* Posts a notification in the notification bar when a transition is detected.
* If the user clicks the notification, control goes to the MainActivity.
*/
private void headsUpSmsNotifaction(String notificationDetails){
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(this, sendSmsReceiver.class);
PendingIntent notificationPendingIntent = PendingIntent.getBroadcast(this,44,notificationIntent,0);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat mdfromat = new SimpleDateFormat("hh:ss | dd/ MM / yyyy");
String strDate = mdfromat.format(calendar.getTime());
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Define the notification settings.
builder.setSmallIcon(R.mipmap.ic_launcher)
// In a real app, you may want to use a library like Volley
// to decode the Bitmap.
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher))
.setColor(Color.RED)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVibrate(new long[]{10,10,10})
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentTitle(notificationDetails)
.setContentText(strDate)
.addAction(R.mipmap.ic_launcher,"send",notificationPendingIntent)
.setContentIntent(notificationPendingIntent);
// Dismiss notification once the user touches it.
//builder.setAutoCancel(true);
// 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 getString(R.string.geofence_transition_entered);
case Geofence.GEOFENCE_TRANSITION_EXIT:
return getString(R.string.geofence_transition_exited);
default:
return getString(R.string.unknown_geofence_transition);
}
}
}
listPageActivty Class:
public class listPageActivty extends AppCompatActivity {
ListView listView;
String[] ListElements = new String[]{"Android","PHP"};
String item = "one";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_page_activty);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
listView = (ListView) findViewById(R.id.listView2);
Button addBtn = (Button) findViewById(R.id.addBtn);
final List<String> ListElementsArrayList = new ArrayList<String>(Arrays.asList(ListElements));
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(listPageActivty.this,android.R.layout.simple_list_item_1,ListElementsArrayList);
listView.setAdapter(adapter);
Bundle bundleData = getIntent().getExtras();
if (bundleData == null){
return;
}
String intentItem = bundleData.getString("intentKey");
ListElementsArrayList.add(intentItem);
addBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ListElementsArrayList.add(item);
adapter.notifyDataSetChanged();
}
});
}
}
Any Help would be Greatly Appreciated, Thank you.
You have to change this :
Intent i = new Intent(this, listPageActivty.class);
i.putExtra("intentKey",geofenceTransitionDetails);
to this :
Intent i = new Intent(this, listPageActivty.class);
i.putExtra("intentKey",geofenceTransitionDetails);
this.startActivity(i);
I think you just left the startActivity()
And in the other Activity you get the value like this :
String GeoTransitionDetails = getIntent().getExtras().getString("intentKey");
You are nowhere starting listPageActivty activity
Replace
Intent i = new Intent(this, listPageActivty.class);
i.putExtra("intentKey",geofenceTransitionDetails);
with
Intent i = new Intent(getBaseContext(), listPageActivty.class);
i.putExtra("intentKey",geofenceTransitionDetails);
getApplication().startActivity(dialogIntent);
So in the app I am developing the user can types in a string and selects an address. When they select done the address is put into a geofence and when they enter that geofence a notification is posted. The problem I am having is when I try to add the strings of a row from that name on notification press. The names are the same yet the cursor can not find the row with the string. Thanks in advance!
Here is the error:
android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1
at android.database.AbstractCursor.checkPosition(AbstractCursor.java:426)
at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:136)
at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:50)
at com.nick.mowen.receiptmanager.ManagerDatabaseAdapter.getDataArray(ManagerDatabaseAdapter.java:64)
at com.nick.mowen.receiptmanager.GeofenceTransitionsIntentService.getAppToOpen(GeofenceTransitionsIntentService.java:35)
at com.nick.mowen.receiptmanager.GeofenceTransitionsIntentService.sendNotification(GeofenceTransitionsIntentService.java:82)
at com.nick.mowen.receiptmanager.GeofenceTransitionsIntentService.onHandleIntent(GeofenceTransitionsIntentService.java:64)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.os.HandlerThread.run(HandlerThread.java:61)
Here is the method to get the String[] of values:
public String[] getDataArray(String name) {
String[] columns = {ManagerHelper.NAME,ManagerHelper.CODE,ManagerHelper.ADDRESS};
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query(ManagerHelper.TABLE_NAME, columns, ManagerHelper.NAME + " = '" + name + "'", null, null, null, null);
String[] data = new String[3];
data[0] = cursor.getString(1);
data[1] = cursor.getString(2);
data[2] = cursor.getString(3);
return data;
}
Here is the code to add the notification and get details:
public String[] getAppToOpen(String names) {
managerDatabaseAdapter = new ManagerDatabaseAdapter(this);
selectArgs = managerDatabaseAdapter.getDataArray(names);
return selectArgs;
}
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = "There is hopefully no error";
Log.e(TAG, errorMessage);
return;
}
// Get the transition type.
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
List triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Get the transition details as a String.
String geofenceTransitionDetails = getGeofenceTransitionDetails(
geofenceTransition,
triggeringGeofences
);
// Send notification and log the transition details.
sendNotification(geofenceTransitionDetails);
//Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
}
}
private String getGeofenceTransitionDetails(int geofenceTransition, List triggeringGeofences) {
String[] split = {":", " "};
String name = triggeringGeofences.get(0).toString();
newSplit = name.split(split[0]);
secondSplit = newSplit[1].split(split[1]);
return secondSplit[0];
}
private void sendNotification(String geofenceTransitionDetails) {
NotificationCompat.Builder builder = (NotificationCompat.Builder) new NotificationCompat.Builder(this).setContentTitle("Receipt Code Reminder").setContentText(geofenceTransitionDetails).setSmallIcon(R.drawable.ic_stat_maps_local_restaurant).setTicker("Receipt Code Reminder").setAutoCancel(true);
Intent localIntent = new Intent(this, ViewCodeActivity.class);
localIntent.putExtra(EXTRA_MESSAGE, getAppToOpen(geofenceTransitionDetails));
TaskStackBuilder localTaskStackBuilder = TaskStackBuilder.create(this);
localTaskStackBuilder.addParentStack(ViewCodeActivity.class);
localTaskStackBuilder.addNextIntent(localIntent);
builder.setContentIntent(localTaskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));
((NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE)).notify(this.mID, builder.build());
}
}
"Index -1 requested" indicates that you are querying a Cursor without first calling cursor.moveToFirst(). Also, it is highly recommended that you use constants with semantic names for your columns, rather than 1, 2 and 3.
Ive added all the necessary code from the google documentation for implementing Geofencing in android. Although I can more or less understand what is going on, im trying to make the process as minimal as possible. Ideally, the user would pick a location and a geofence gets set around it. Once user enters the area, it should trigger an action. For now I am just trying to hard-code a location and get that working then take it from there. With that being said the button stuff is probably not necessary or the array-list of specified events.
My question is what is the bare minimum code I need to implement this process and what is the best way to go about it? This is my first android app so go easy on the heckling.
public class MapsActivity extends FragmentActivity implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
// Kick off the request to build GoogleApiClient.
buildGoogleApiClient();
mGeofencePendingIntent = null;
// Get the value of mGeofencesAdded from SharedPreferences. Set to false as a default.
mGeofencesAdded = mSharedPreferences.getBoolean(Constants.GEOFENCES_ADDED_KEY, false);
// Get the geofences used. Geofence data is hard coded in this sample.
populateGeofenceList();
getGeofencePendingIntent();
getGeofencingRequest();
addGeofencesButtonHandler(this);
//Get the UI widgets.
mAddGeofencesButton = (Button) findViewById(R.id.add_geofences_button);
mRemoveGeofencesButton = (Button) findViewById(R.id.remove_geofences_button);
mSharedPreferences = getSharedPreferences(Constants.SHARED_PREFERENCES_NAME,
MODE_PRIVATE);
}
}
#Override
public void onConnected(Bundle bundle) {
mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(1000); // Update location every second
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, this);
}
#Override
public void onLocationChanged(Location location) {
double lat = location.getLatitude();
double lng = location.getLongitude();
myLat = lat;
myLng = lng;
mapCenter = new LatLng(myLat, myLng);
mMap.moveCamera(CameraUpdateFactory.newLatLng(mapCenter));
}
/**
* Builds a GoogleApiClient. Uses the {#code #addApi} method to request the LocationServices API.
*/
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
private void setUpMap() {
// Enable MyLocation Layer of Google Map
mMap.setMyLocationEnabled(true);
// Get LocationManager object from System Service LOCATION_SERVICE
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// set map type
mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
// Get the name of the best provider
String networkProvider = locationManager.NETWORK_PROVIDER;
String gpsProvider = locationManager.GPS_PROVIDER;
// Get Best Current Location
myLocation = locationManager.getLastKnownLocation(networkProvider);
// Get latitude of the current location
double latitude = myLocation.getLatitude();
// Get longitude of the current location
double longitude = myLocation.getLongitude();
// Create a LatLng object for the current location
LatLng latLng = new LatLng(latitude, longitude);
// Show the current location in Google Map
mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
// Zoom in the Google Map
mMap.animateCamera(CameraUpdateFactory.zoomTo(14));
//Add Marker For event
Intent myIntent = getIntent();
String desc = myIntent.getStringExtra("desc");
String addr = myIntent.getStringExtra("addr");
venueLat = myIntent.getDoubleExtra("lat", 0.0);
venueLng = myIntent.getDoubleExtra("lon", 0.0);
mMap.addMarker(new MarkerOptions().position(new LatLng(venueLat, venueLng)).title(desc).snippet(addr));
}
}
/*
*
*
*
*
* Geofence Stuff
*
*
*
*
*/
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
// calling addGeofences() and removeGeofences().
return PendingIntent.getService(this, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
}
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeofenceList);
return builder.build();
}
/**
* This sample hard codes geofence data. A real app might dynamically create geofences based on
* the users location.
*/
public void populateGeofenceList() {
for (Map.Entry<String, LatLng> entry : Constants.BAY_AREA_LANDMARKS.entrySet()) {
mGeofenceList.add(new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId(entry.getKey())
// Set the circular region of this geofence.
.setCircularRegion(
entry.getValue().latitude,
entry.getValue().longitude,
Constants.GEOFENCE_RADIUS_IN_METERS
)
// Set the expiration duration of the geofence. This geofence gets automatically
// removed after this period of time.
.setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
// Set the transition types of interest. Alerts are only generated for these
// transition. We track entry and exit transitions in this sample.
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
// Create the geofence.
.build());
}
}
public void addGeofencesButtonHandler(View view) {
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
}
try {
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
// 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((ResultCallback<Status>) this); // Result processed in onResult().
} catch (SecurityException securityException) {
// Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
// logSecurityException(securityException);
}
}
/**
* Runs when the result of calling addGeofences() and removeGeofences() becomes available.
* Either method can complete successfully or with an error.
*
* Since this activity implements the {#link ResultCallback} interface, we are required to
* define this method.
*
* #param status The Status returned through a PendingIntent when addGeofences() or
* removeGeofences() get called.
*/
public void onResult(Status status) {
if (status.isSuccess()) {
// Update state and save in shared preferences.
mGeofencesAdded = !mGeofencesAdded;
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Constants.GEOFENCES_ADDED_KEY, mGeofencesAdded);
editor.commit();
// Update the UI. Adding geofences enables the Remove Geofences button, and removing
// geofences enables the Add Geofences button.
setButtonsEnabledState();
Toast.makeText(
this,
getString(mGeofencesAdded ? R.string.geofences_added :
R.string.geofences_removed),
Toast.LENGTH_SHORT
).show();
} else {
// Get the status code for the error and log it using a user-friendly message.
String errorMessage = GeofenceErrorMessages.getErrorString(this,
status.getStatusCode());
Log.e(TAG, errorMessage);
}
}
/**
* Ensures that only one button is enabled at any time. The Add Geofences button is enabled
* if the user hasnt yet added geofences. The Remove Geofences button is enabled if the
* user has added geofences.
*/
private void setButtonsEnabledState() {
if (mGeofencesAdded) {
mAddGeofencesButton.setEnabled(false);
mRemoveGeofencesButton.setEnabled(true);
} else {
mAddGeofencesButton.setEnabled(true);
mRemoveGeofencesButton.setEnabled(false);
}
}
}
/*
* ---------------------------------------------------------------------------------------
*/
public class GeofenceTransitionsIntentService extends IntentService {
protected static final String TAG = "geofence-transitions-service";
/**
* This constructor is required, and calls the super IntentService(String)
* constructor with the name for a worker thread.
*/
public GeofenceTransitionsIntentService() {
// Use the TAG to name the worker thread.
super(TAG);
}
#Override
public void onCreate() {
super.onCreate();
}
/**
* Handles incoming intents.
* #param intent sent by Location Services. This Intent is provided to Location
* Services (inside a PendingIntent) when addGeofences() is called.
*/
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.getErrorCode());
Log.e(TAG, errorMessage);
return;
}
// Get the transition type.
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
// Get the geofences that were triggered. A single event can trigger multiple geofences.
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Get the transition details as a String.
String geofenceTransitionDetails = getGeofenceTransitionDetails(
this,
geofenceTransition,
triggeringGeofences
);
// Send notification and log the transition details.
sendNotification(geofenceTransitionDetails);
// Toast.makeText(getApplicationContext(), geofenceTransitionDetails, Toast.LENGTH_SHORT).show();
Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition));
}
}
/**
* Gets transition details and returns them as a formatted string.
*
* #param context The app context.
* #param geofenceTransition The ID of the geofence transition.
* #param triggeringGeofences The geofence(s) triggered.
* #return The transition details formatted as String.
*/
private String getGeofenceTransitionDetails(
Context context,
int geofenceTransition,
List<Geofence> triggeringGeofences) {
String geofenceTransitionString = getTransitionString(geofenceTransition);
// Get the Ids of each geofence that was triggered.
ArrayList triggeringGeofencesIdsList = new ArrayList();
for (Geofence geofence : triggeringGeofences) {
triggeringGeofencesIdsList.add(geofence.getRequestId());
}
String triggeringGeofencesIdsString = TextUtils.join(", ", triggeringGeofencesIdsList);
return geofenceTransitionString + ": " + triggeringGeofencesIdsString;
}
/**
* Posts a notification in the notification bar when a transition is detected.
* If the user clicks the notification, control goes to the MainActivity.
*/
private void sendNotification(String notificationDetails) {
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(getApplicationContext(), MapsActivity.class);
// Construct a task stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Add the main Activity to the task stack as the parent.
stackBuilder.addParentStack(MapsActivity.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 thats compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Define the notification settings.
builder.setSmallIcon(R.drawable.ic_launcher)
// In a real app, you may want to use a library like Volley
// to decode the Bitmap.
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher))
.setColor(Color.RED)
.setContentTitle(notificationDetails)
.setContentText(getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Dismiss notification once the user touches it.
builder.setAutoCancel(true);
// 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 getString(R.string.geofence_transition_entered);
case Geofence.GEOFENCE_TRANSITION_EXIT:
return getString(R.string.geofence_transition_exited);
default:
return getString(R.string.unknown_geofence_transition);
}
}
}
/*
* --------------------------------------------------------------------------
*/
public class Constants {
private Constants() {
}
public static final String PACKAGE_NAME = "com.google.android.gms.location.Geofence";
public static final String SHARED_PREFERENCES_NAME = PACKAGE_NAME + ".SHARED_PREFERENCES_NAME";
public static final String GEOFENCES_ADDED_KEY = PACKAGE_NAME + ".GEOFENCES_ADDED_KEY";
/**
* Used to set an expiration time for a geofence. After this amount of time Location Services
* stops tracking the geofence.
*/
public static final long GEOFENCE_EXPIRATION_IN_HOURS = 12;
/**
* For this sample, geofences expire after twelve hours.
*/
public static final long GEOFENCE_EXPIRATION_IN_MILLISECONDS =
GEOFENCE_EXPIRATION_IN_HOURS * 60 * 60 * 1000;
public static final float GEOFENCE_RADIUS_IN_METERS = 1609; // 1 mile, 1.6 km
/**
* Map for storing information about airports in the San Francisco bay area.
*/
public static final HashMap<String, LatLng> BAY_AREA_LANDMARKS = new HashMap<String, LatLng>();
static {
BAY_AREA_LANDMARKS.put("Home", new LatLng(29.382798, -98.529470));
BAY_AREA_LANDMARKS.put("Other Home", new LatLng(29.472491,-98.571244));
}
}
Here are few suggestions regarding your code:
Try to make it more modular. Separate out the code in different files according to the functionality you are expecting them to support (eg, maps code can be separated from location etc).
Make sure your app has the latest version of Google Play Services and making use of the FusedLocationApi.
Make sure the geofence radius is altelast 100 meters. Else the enter exit code wont be triggered.
Add the necessary permissions in the manifest files such as ACCESS_FINE_LOCATION, BroadCastreceiver (if you are using) etc.
Please refer to the following tutorial for code implementation.
I'mm trying to create an app that allows you to set a proximity alert for marker when you click on it's info window.
googleMap.setOnInfoWindowClickListener(
new OnInfoWindowClickListener(){
public void onInfoWindowClick(Marker marker) {
LatLng clickedMarkerLatLng = marker.getPosition();
double lat = clickedMarkerLatLng.latitude;
double long1 = clickedMarkerLatLng.longitude;
Log.e("hello", "Output=" + lat + long1);
LocationManager lm;
// double lat=123,long1=34; //Defining Latitude & Longitude
float radius=30; //Defining Radius
lm=(LocationManager) getSystemService(Context.LOCATION_SERVICE);
Intent i= new Intent("com.example.sleepertrain5.ProximityReceiver"); //Custom Action
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 1, i, PendingIntent.FLAG_CANCEL_CURRENT);
lm.addProximityAlert(lat, long1, radius, -1, pendingIntent);
}
This is the code that calls the Broadcast Receiver
public class ProximityReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context arg0, Intent arg1) {
// TODO Auto-generated method stub
// The reciever gets the Context & the Intent that fired the broadcast as arg0 & agr1
String k=LocationManager.KEY_PROXIMITY_ENTERING;
// Key for determining whether user is leaving or entering
boolean state=arg1.getBooleanExtra(k, false);
//Gives whether the user is entering or leaving in boolean form
if(state){
// Call the Notification Service or anything else that you would like to do here
Toast.makeText(arg0, "Welcome to my Area", 600).show();
}else{
//Other custom Notification
Toast.makeText(arg0, "Thank you for visiting my Area,come back again !!", 600).show();
}
}
The above is the Broadcast receiver. None of this works and I can't figure out why. Any help would be great.
Use below code for Pending Intent:
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
nId, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
Where nId will be any uniq number that represent particular pending intent.