I have written an android app which basically allows me to keep track of times and address of GPS coordinates.
I have 3 Lists each corresponding to Lyft, Uber and Other.
But I believe my app starts slowing down my Smart Phone (Samsung Galaxy S7 Edge, with Android O)
can someone look at my code and tell me why is it slowing my smart phone.
My assumption is that, possibly thread synchronization issue.
Attached is my code
1) MainActivity.java
package com.milind.myapp.gpstrackingservice;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.os.PowerManager;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements AddressListener
{
private static final String TAG = MainActivity.class.getSimpleName();
private PowerManager.WakeLock wakeLock;
private TextView labelAddress;
private TextView multiTextLyft;
private TextView multiTextUber;
private TextView multiTextOther;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
labelAddress = findViewById(R.id.label_address);
multiTextLyft = findViewById(R.id.multi_text_lyft);
multiTextUber = findViewById(R.id.multi_text_uber);
multiTextOther = findViewById(R.id.multi_text_other);
if (MyService.isServiceStarted())
{
MyService.getInstance().load(getSharedPrefs());
refreshAllViews();
}
PowerManager powerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "myapp:My Lock");
}
#Override
protected void onStart()
{
super.onStart();
ActivityCompat.requestPermissions(this, new String[]
{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WAKE_LOCK}, 1);
if (!MyService.isServiceStarted())
{
Intent intent = new Intent(this, MyService.class);
intent.setAction(MyService.ACTION_START_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
startForegroundService(intent);
}
else
{
startService(intent);
}
}
}
#Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
{
if (MyService.isServiceStarted())
{
MyService.getInstance().registerAddressListener(this);
}
wakeLock.acquire();
}
else
{
if (MyService.isServiceStarted())
{
MyService.getInstance().unregisterAddressListener(this);
}
wakeLock.release();
}
}
public void onRequestPermissionsResult(int requestCode, String permissions[],
int[] grantResults)
{
switch (requestCode)
{
case 1:
{
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
}
else
{
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
#Override
public void onLocationChanged(Location loc)
{
final String address = MyService.getAddress(this, loc);
final CharSequence text = labelAddress.getText();
if (text.toString().equals(address))
{
return;
}
MyService.getInstance().load(getSharedPrefs());
labelAddress.setText(address);
refreshAllViews();
new Tone().play(880);
}
private void refreshAllViews()
{
runOnUiThread(new Runnable()
{
public void run()
{
refereshEditTextLyft();
refereshEditTextUber();
refereshEditTextOther();
}
});
}
private void refereshEditTextLyft()
{
multiTextLyft.setText(MyService.getInstance().getLyftAddresses());
}
private void refereshEditTextUber()
{
multiTextUber.setText(MyService.getInstance().getUberAddresses());
}
private void refereshEditTextOther()
{
multiTextOther.setText(MyService.getInstance().getOtherAddresses());
}
private SharedPreferences getSharedPrefs()
{
return getSharedPreferences("name", MODE_PRIVATE);
}
public void onLyftButtonClicked(View view)
{
MyService.getInstance().addLyftAddress(labelAddress.getText());
new Tone().play(440);
refereshEditTextLyft();
MyService.getInstance().save(getSharedPrefs());
}
public void onUberButtonClicked(View view)
{
MyService.getInstance().addUberAddress(labelAddress.getText());
new Tone().play(440);
refereshEditTextUber();
MyService.getInstance().save(getSharedPrefs());
}
public void onOtherButtonClicked(View view)
{
MyService.getInstance().addOtherAddress(labelAddress.getText());
new Tone().play(440);
refereshEditTextOther();
MyService.getInstance().save(getSharedPrefs());
}
public void onClearButtonClicked(View view)
{
if (MyService.isServiceStarted())
{
SharedPreferences sharedPreferences = getSharedPrefs();
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
MyService.getInstance().clear();
refereshEditTextLyft();
refereshEditTextUber();
refereshEditTextOther();
}
}
public void onDelLyftButtonClicked(View view)
{
MyService.getInstance().delLyftEntry();
new Tone().play(440);
refereshEditTextLyft();
MyService.getInstance().save(getSharedPrefs());
}
public void onDelUberButtonClicked(View view)
{
MyService.getInstance().delUberEntry();
new Tone().play(440);
refereshEditTextUber();
MyService.getInstance().save(getSharedPrefs());
}
public void onDelOtherButtonClicked(View view)
{
MyService.getInstance().delOtherEntry();
new Tone().play(440);
refereshEditTextOther();
MyService.getInstance().save(getSharedPrefs());
}
#Override
protected void onRestart()
{
super.onRestart();
}
#Override
public void onActionModeFinished(ActionMode mode)
{
super.onActionModeFinished(mode);
}
#Override
public void onBackPressed()
{
super.onBackPressed();
}
}
2) The MyService.java
package com.milind.myapp.gpstrackingservice;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class MyService extends Service implements LocationListener
{
private static final String TAG = MyService.class.getSimpleName();
public static final String ACTION_START_SERVICE = "ACTION_START_SERVICE";
public static final String ACTION_STOP_SERVICE = "ACTION_STOP_SERVICE";
public static final String ACTION_UBER = "ACTION_UBER";
public static final String ACTION_LYFT = "ACTION_UBER";
public static final String ACTION_END = "ACTION_END";
public static final String LYFT_PREFIX = "Lyft";
public static final String OTHER_PREFIX = "Other";
public static final String UBER_PREFIX = "Uber";
private static MyService mInstance = null;
private List<AddressListener> listeners = new ArrayList<>();
private List<AddressPoint> lyftAddresses = new ArrayList<>();
private List<AddressPoint> uberAddresses = new ArrayList<>();
private List<AddressPoint> otherAddresses = new ArrayList<>();
private Location mLastLocation;
public MyService()
{
super();
Log.d(TAG, "MyService(): constructor called");
}
public static boolean isServiceStarted()
{
Log.d(TAG, "isServiceStarted()");
return mInstance != null;
}
public static final MyService getInstance()
{
return mInstance;
}
#Override
public IBinder onBind(Intent intent)
{
Log.d(TAG, "onBind(Intent intent)");
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Log.d(TAG, "onStartCommand(Intent, flags, startId) : " + hashCode());
Log.d(TAG, "onStartCommand(...) intent=" + intent + "=" + ", flags=" + flags + ", startId=" + startId);
Log.d(TAG, "onStartCommand(...) isServiceStarted=" + isServiceStarted());
String action = null;
if (intent != null)
{
Log.d(TAG, intent.toString());
action = intent.getAction();
}
else
{
Log.d(TAG, "onStartCommand(...): early return");
return super.onStartCommand(intent, flags, startId);
}
if (isServiceStarted() == false && action == ACTION_START_SERVICE)
{
Log.d(TAG, "onStartCommand(...): Service starting=" + startId);
//startForegroundServivceNotification()
startRunningInForeground();
requestLocationUpdates();
mInstance = this;
Log.d(TAG, "onStartCommand(...): Service started=" + startId);
}
else if (isServiceStarted() == true && action == ACTION_STOP_SERVICE)
{
Log.d(TAG, "onStartCommand(...): Service stopping=" + startId);
stopLocationUpdates();
stopSelf();
Log.d(TAG, "onStartCommand(...): Service stop requested" + startId);
mInstance = null;
}
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy()
{
Log.d(TAG, "onDestroy(): Service destroyed");
super.onDestroy();
mInstance = null;
}
private void stopLocationUpdates()
{
Log.d(TAG, "stopLocationUpdates()");
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationManager.removeUpdates(this);
}
private void requestLocationUpdates()
{
Log.d(TAG, "requestLocationUpdates()");
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
try
{
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1500, 0, this);
Log.w(TAG, "requestLocationUpdates(): ended gracefully");
}
catch (SecurityException ex)
{
Log.w(TAG, "requestLocationUpdates(): Exception");
ex.printStackTrace();
}
}
#Override
public void onLocationChanged(Location location)
{
Log.v(TAG, "onLocationChanged(Location location): started, location=" + location.toString());
dispatchLocationChange(location);
mLastLocation = location;
Log.v(TAG, "onLocationChanged: completed");
}
private void dispatchLocationChange(Location loc)
{
Log.v(TAG, "dispatchLocationChange(Location)");
for (AddressListener listener : listeners)
{
listener.onLocationChanged(loc);
}
}
public static String getAddress(Context context, Location loc)
{
Log.v(TAG, "getAddress(Location loc) started");
List<Address> addresses;
Geocoder gcd = new Geocoder(context, Locale.getDefault());
try
{
addresses = gcd.getFromLocation(loc.getLatitude(),
loc.getLongitude(), 1);
String strReturnAddress = "";
if (addresses != null && addresses.size() > 0)
{
final Address address = addresses.get(0);
Log.d(TAG, address.toString());
String addressLines = "";
Log.v(TAG, "Locale: " + address.getLocale());
for (int i = 0; i <= address.getMaxAddressLineIndex(); ++i)
{
Log.v(TAG, "AddressLine " + i + ": " + address.getAddressLine(i));
addressLines += address.getAddressLine(i) + ", ";
Log.v(TAG, "addressLines:" + addressLines);
}
String strAddress =
addressLines
;
Log.v(TAG, "strAddress:" + strAddress);
strReturnAddress = strAddress.substring(0, strAddress.length() - 2);
Log.v(TAG, "strReturnAddress:" + strReturnAddress);
}
Log.d(TAG, "getAddress(Location loc) completed with return=" + strReturnAddress);
return strReturnAddress;
}
catch (IOException e)
{
e.printStackTrace();
Log.d(TAG, "Exception", e);
}
Log.d(TAG, "getAddress(Location loc) completed with return=null");
return "";
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
Log.d(TAG, "onStatusChanged(provider, status, extras): status=" + status + ", extras=" + extras);
}
#Override
public void onProviderEnabled(String provider)
{
Log.d(TAG, "onProviderEnabled(provider) ");
}
#Override
public void onProviderDisabled(String provider)
{
Log.d(TAG, "onProviderDisabled(provider) ");
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
private static void logGetProperties(final String tag, final Object obj)
{
Log.v(tag, "logGetProperties(...)");
Class cls = obj.getClass();
Method[] methods = cls.getMethods();
Log.v(tag, "methods.length = " + methods.length);
for (Method method : methods)
{
String methodName = method.getName();
Log.v(tag, "methodName = " + methodName);
if (methodName.startsWith("get")
&& (method.getParameters() == null || method.getParameters().length == 0)
&& method.getReturnType() != Void.class)
{
try
{
Log.v(tag, methodName + " = " + method.invoke(obj, new Object[0]));
}
catch (Exception ex)
{
Log.e(tag, methodName + " Failed (exception)");
}
}
}
}
public AddressListener registerAddressListener(AddressListener listener)
{
Log.d(TAG, "registerAddressListener(AddressListener)");
if (!listeners.contains(listener))
{
listeners.add(listener);
}
return listener;
}
public AddressListener unregisterAddressListener(AddressListener listener)
{
Log.d(TAG, "unregisterAddressListener(AddressListener)");
if (listeners.contains(listener))
{
listeners.remove(listener);
Log.d(TAG, "unregisterAddressListener(AddressListener): Listener removed");
return listener;
}
Log.d(TAG, "unregisterAddressListener(AddressListener): Listener not found");
return null;
}
public void addLyftAddress(CharSequence text)
{
Log.d(TAG, "addLyftAddress(CharSequence text): text: " + text);
lyftAddresses.add(new AddressPoint(System.currentTimeMillis(), text));
}
public void addUberAddress(CharSequence text)
{
Log.d(TAG, "addUberAddress(CharSequence text): text: " + text);
uberAddresses.add(new AddressPoint(System.currentTimeMillis(), text));
}
public void addOtherAddress(CharSequence text)
{
Log.d(TAG, "addOtherAddress(CharSequence text): text: " + text);
otherAddresses.add(new AddressPoint(System.currentTimeMillis(), text));
}
String getLyftAddresses()
{
return getAddresses(lyftAddresses);
}
String getUberAddresses()
{
return getAddresses(uberAddresses);
}
String getOtherAddresses()
{
return getAddresses(otherAddresses);
}
private String getAddresses(List<AddressPoint> addresses)
{
Log.d(TAG, "getAddresses(List<AddressPoint>)");
String strAddresses = "" + addresses.size() + "\n--------\n";
for (int i = 0; i < addresses.size(); ++i)
{
AddressPoint addresspoint = addresses.get(i);
strAddresses += addresspoint + "\n--------\n";
if ((i % 2) != 0)
{
strAddresses += "\n\n";
}
}
return strAddresses;
}
private void startRunningInForeground()
{
//if more than or equal to 26
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
//if more than 26
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
{
String CHANNEL_ONE_ID = "Package.Service";
String CHANNEL_ONE_NAME = "Screen service";
NotificationChannel notificationChannel = null;
notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,
CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_MIN);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setShowBadge(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (manager != null)
{
manager.createNotificationChannel(notificationChannel);
}
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
Notification notification = new Notification.Builder(getApplicationContext())
.setChannelId(CHANNEL_ONE_ID)
.setContentTitle("Recording data")
.setContentText("App is running background operations")
.setSmallIcon(R.drawable.ic_launcher_background)
.setLargeIcon(icon)
.build();
Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
notification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0);
startForeground(101, notification);
}
//if version 26
else
{
startForeground(101, updateNotification());
}
}
//if less than version 26
else
{
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("App")
.setContentText("App is running background operations")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setOngoing(true).build();
startForeground(101, notification);
}
}
private Notification updateNotification()
{
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0);
return new NotificationCompat.Builder(this)
.setContentTitle("Activity log")
.setTicker("Ticker")
.setContentText("app is running background operations")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setOngoing(true).build();
}
public void load(final SharedPreferences lSharedPrefs)
{
Log.w(TAG, "load(SharedPreferences): started");
load(lSharedPrefs, lyftAddresses, LYFT_PREFIX);
load(lSharedPrefs, uberAddresses, UBER_PREFIX);
load(lSharedPrefs, otherAddresses, OTHER_PREFIX);
Log.w(TAG, "load(SharedPreferences): completed");
}
private void load(final SharedPreferences lSharedPrefs, final List<AddressPoint> lAddrPoints, final String lPrefix)
{
Log.w(TAG, "load(SharedPreferences, lAddrPoints, lPrefix)");
lAddrPoints.clear();
final int count = lSharedPrefs.getInt(lPrefix + "Count", lAddrPoints.size());
for (int i = 0; i < count; ++i)
{
String address = lSharedPrefs.getString(lPrefix + "Address" + i, null);
final long time = lSharedPrefs.getLong(lPrefix + "Time" + i, 0);
//if address or time is invalid skip to the next entry
if (address == null || time == 0)
{
continue;
}
final AddressPoint addressPoint = new AddressPoint(time, address);
lAddrPoints.add(addressPoint);
}
}
private void save(final SharedPreferences lSharedPrefs, final List<AddressPoint> lAddrPoints, final String lPrefix)
{
Log.w(TAG, "save(SharedPreferences, lAddrPoints, lPrefix)");
SharedPreferences.Editor editor = lSharedPrefs.edit();
final int count = lAddrPoints.size();
//Save the count
editor.putInt(lPrefix + "Count", count);
for (int i = 0; i < count; ++i)
{
//Save the entry
AddressPoint lAddrPoint = lAddrPoints.get(i);
editor.putLong(lPrefix + "Time" + i, lAddrPoint.getTime());
editor.putString(lPrefix + "Address" + i, (String) lAddrPoint.getAddress());
}
Log.w(TAG, "save(sharedFrefs, List, String): commit");
editor.commit();
}
public void save(final SharedPreferences sharedPreferences)
{
Log.w(TAG, "save(SharedPreferences): started");
Log.w(TAG, "save: lyftAddresses");
save(sharedPreferences, lyftAddresses, LYFT_PREFIX);
Log.w(TAG, "save: uberAddresses");
save(sharedPreferences, uberAddresses, UBER_PREFIX);
Log.w(TAG, "save: otherAddresses");
save(sharedPreferences, otherAddresses, OTHER_PREFIX);
Log.w(TAG, "save(SharedPreferences) completed");
}
public void clear()
{
lyftAddresses.clear();
uberAddresses.clear();
otherAddresses.clear();
}
public void delLyftEntry()
{
if (lyftAddresses.size() > 0)
{
lyftAddresses.remove(lyftAddresses.size() - 1);
}
}
public void delUberEntry()
{
if (uberAddresses.size() > 0)
{
uberAddresses.remove(uberAddresses.size() - 1);
}
}
public void delOtherEntry()
{
if (otherAddresses.size() > 0)
{
otherAddresses.remove(otherAddresses.size() - 1);
}
}
}
3) activity_main.xml
4) AndroidManifest.xml
This (likely) isn't a threading issue, in the normal sense of the word. What's happening is you've got a GeoCoder.getFromLocation() call executing on the main thread. Per the documentation:
The returned values may be obtained by means of a network lookup. ...It may be useful to call this method from a thread separate from your primary UI thread.
That means the method could block for several seconds each time it is called. That's more likely if you're driving through an area of spotty cell coverage. Since the method is called with each location update (roughly every 2 seconds), it's understandable that the UI is hanging.
SUGGESTED FIX
Replace your getAddress() function with an AsyncTask, which moves the getFromLocation() call to a background thread (now your app will truly be multithreaded). Something like this should work:
private class GetFromLocationTask extends AsyncTask<Location, Void, List<Address>> {
protected List<Address> doInBackground(Location... locs) {
return gcd.getFromLocation(locs[ 0 ].getLatitude(), locs[ 0 ].getLongitude(), 1);
}
protected void onProgressUpdate(Void... progress) {}
protected void onPostExecute(List<Address> result) {
//execute the remainder of your getAddress() logic here
}
}
Then, execute it using new GetFromLocationTask().execute(location). Call this instead of getAddress(). You don't need to pass a Context to getAddress(), since Service.this will work just as well (it is a Context).
Bonus hint: Note that onLocationChanged() runs on the UI thread, and so does refreshAllViews(). That means your call to runOnUiThread() is superfluous, and it will just execute the given Runnable synchronously.
Related
I am trying to create push notification for my cordova project. I followed the tutorial from this link http://devgirl.org/2012/10/25/tutorial-android-push-notifications-with-phonegap/ and created the plugin and I see the FCM is not sending back the registration id. I have added sender id in my application. Please help me to get this done. Thanks in advance.
GCMIntentService.java
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.android.gcm.GCMBaseIntentService;
#SuppressLint("NewApi")
public class GCMIntentService extends GCMBaseIntentService {
private static final String TAG = "GCMIntentService";
public GCMIntentService() {
super("GCMIntentService");
}
#Override
public void onRegistered(Context context, String regId) {
Log.v(TAG, "onRegistered: "+ regId);
JSONObject json;
try
{
json = new JSONObject().put("event", "registered");
json.put("regid", regId);
Log.v(TAG, "onRegistered: " + json.toString());
// Send this JSON data to the JavaScript application above EVENT should be set to the msg type
// In this case this is the registration ID
PushPlugin.sendJavascript( json );
}
catch( JSONException e)
{
// No message to the user is sent, JSON failed
Log.e(TAG, "onRegistered: JSON exception");
}
}
#Override
public void onUnregistered(Context context, String regId) {
Log.d(TAG, "onUnregistered - regId: " + regId);
}
#Override
protected void onMessage(Context context, Intent intent) {
Log.d(TAG, "onMessage - context: " + context);
// Extract the payload from the message
Bundle extras = intent.getExtras();
if (extras != null)
{
// if we are in the foreground, just surface the payload, else post it to the statusbar
if (PushPlugin.isInForeground()) {
extras.putBoolean("foreground", true);
createNotification(context, extras);
PushPlugin.sendExtras(extras);
}
else {
extras.putBoolean("foreground", false);
// Send a notification if there is a message
if (extras.getString("message") != null && extras.getString("message").length() != 0) {
createNotification(context, extras);
}
}
}
}
public void createNotification(Context context, Bundle extras)
{
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
String appName = getAppName(this);
Intent notificationIntent = new Intent(this, PushHandlerActivity.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
notificationIntent.putExtra("pushBundle", extras);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
int defaults = Notification.DEFAULT_ALL;
if (extras.getString("defaults") != null) {
try {
defaults = Integer.parseInt(extras.getString("defaults"));
} catch (NumberFormatException e) {}
}
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(context)
.setDefaults(defaults)
.setSmallIcon(context.getApplicationInfo().icon)
.setWhen(System.currentTimeMillis())
.setContentTitle(extras.getString("title"))
.setContentText(extras.getString("title"))
.setTicker(extras.getString("title"))
.setContentIntent(contentIntent)
.setAutoCancel(true);
String message = extras.getString("message");
if (message != null) {
mBuilder.setContentText(message);
} else {
mBuilder.setContentText("<missing message content>");
}
String msgcnt = extras.getString("msgcnt");
if (msgcnt != null) {
mBuilder.setNumber(Integer.parseInt(msgcnt));
}
int notId = 0;
try {
notId = Integer.parseInt(extras.getString("notId"));
}
catch(NumberFormatException e) {
Log.e(TAG, "Number format exception - Error parsing Notification ID: " + e.getMessage());
}
catch(Exception e) {
Log.e(TAG, "Number format exception - Error parsing Notification ID" + e.getMessage());
}
mNotificationManager.notify((String) appName, notId, mBuilder.build());
}
private static String getAppName(Context context)
{
CharSequence appName =
context
.getPackageManager()
.getApplicationLabel(context.getApplicationInfo());
return (String)appName;
}
#Override
public void onError(Context context, String errorId) {
Log.e(TAG, "onError - errorId: " + errorId);
}
}
PushPlugin.Java
import java.util.Iterator;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.google.android.gcm.GCMRegistrar;
import android.os.Bundle;
import android.util.Log;
public class PushPlugin extends Plugin {
public static final String TAG = "PushPlugin";
public static final String REGISTER = "register";
public static final String UNREGISTER = "unregister";
public static final String EXIT = "exit";
private static CordovaWebView gWebView;
private static String gECB;
private static String gSenderID;
private static Bundle gCachedExtras = null;
private static boolean gForeground = false;
#Override
public PluginResult execute(String action, JSONArray data, String CallbackContext) {
// TODO Auto-generated method stub
boolean result = false;
Log.v(TAG, "execute: action=" + action);
if (REGISTER.equals(action)) {
Log.v(TAG, "execute: data=" + data.toString());
try {
JSONObject jo = data.getJSONObject(0);
gWebView = this.webView;
Log.v(TAG, "execute: jo=" + jo.toString());
gECB = (String) jo.get("ecb");
gSenderID = (String) jo.get("senderID");
Log.v(TAG, "execute: ECB=" + gECB + " senderID=" + gSenderID);
GCMRegistrar.register(this.cordova.getActivity(), gSenderID);
result = true;
// callbackContext.success();
} catch (JSONException e) {
Log.e(TAG, "execute: Got JSON Exception " + e.getMessage());
result = false;
// callbackContext.error(e.getMessage());
}
if ( gCachedExtras != null) {
Log.v(TAG, "sending cached extras");
sendExtras(gCachedExtras);
gCachedExtras = null;
}
}else if (UNREGISTER.equals(action)) {
GCMRegistrar.unregister(this.cordova.getActivity());
Log.v(TAG, "UNREGISTER");
result = true;
// callbackContext.success();
} else {
result = false;
Log.e(TAG, "Invalid action : " + action);
//callbackContext.error("Invalid action : " + action);
}
return new PluginResult(PluginResult.Status.OK);
//return null;
}
public static void sendExtras(Bundle extras)
{
if (extras != null) {
if (gECB != null && gWebView != null) {
sendJavascript(convertBundleToJson(extras));
} else {
Log.v(TAG, "sendExtras: caching extras to send at a later time.");
gCachedExtras = extras;
}
}
}
private static JSONObject convertBundleToJson(Bundle extras)
{
try
{
JSONObject json;
json = new JSONObject().put("event", "message");
JSONObject jsondata = new JSONObject();
Iterator<String> it = extras.keySet().iterator();
while (it.hasNext())
{
String key = it.next();
Object value = extras.get(key);
// System data from Android
if (key.equals("from") || key.equals("collapse_key"))
{
json.put(key, value);
}
else if (key.equals("foreground"))
{
json.put(key, extras.getBoolean("foreground"));
}
else if (key.equals("coldstart"))
{
json.put(key, extras.getBoolean("coldstart"));
}
else
{
// Maintain backwards compatibility
if (key.equals("message") || key.equals("msgcnt") || key.equals("soundname"))
{
json.put(key, value);
}
if ( value instanceof String ) {
// Try to figure out if the value is another JSON object
String strValue = (String)value;
if (strValue.startsWith("{")) {
try {
JSONObject json2 = new JSONObject(strValue);
jsondata.put(key, json2);
}
catch (Exception e) {
jsondata.put(key, value);
}
// Try to figure out if the value is another JSON array
}
else if (strValue.startsWith("["))
{
try
{
JSONArray json2 = new JSONArray(strValue);
jsondata.put(key, json2);
}
catch (Exception e)
{
jsondata.put(key, value);
}
}
else
{
jsondata.put(key, value);
}
}
}
} // while
json.put("payload", jsondata);
Log.v(TAG, "extrasToJSON: " + json.toString());
return json;
}
catch( JSONException e)
{
Log.e(TAG, "extrasToJSON: JSON exception");
}
return null;
}
public static void sendJavascript(JSONObject _json) {
String _d = "javascript:" + gECB + "(" + _json.toString() + ")";
Log.v(TAG, "sendJavascript: " + _d);
if (gECB != null && gWebView != null) {
gWebView.sendJavascript(_d);
}
}
public static boolean isInForeground()
{
return gForeground;
}
public static boolean isActive()
{
return gWebView != null;
}
}
PushHandlerActivity.java
import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
public class PushHandlerActivity extends Activity
{
private static String TAG = "PushHandlerActivity";
/*
* this activity will be started if the user touches a notification that we own.
* We send it's data off to the push plugin for processing.
* If needed, we boot up the main activity to kickstart the application.
* #see android.app.Activity#onCreate(android.os.Bundle)
*/
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.v(TAG, "onCreate");
boolean isPushPluginActive = PushPlugin.isActive();
processPushBundle(isPushPluginActive);
finish();
if (!isPushPluginActive) {
forceMainActivityReload();
}
}
/**
* Takes the pushBundle extras from the intent,
* and sends it through to the PushPlugin for processing.
*/
private void processPushBundle(boolean isPushPluginActive)
{
Bundle extras = getIntent().getExtras();
if (extras != null) {
Bundle originalExtras = extras.getBundle("pushBundle");
originalExtras.putBoolean("foreground", false);
originalExtras.putBoolean("coldstart", !isPushPluginActive);
PushPlugin.sendExtras(originalExtras);
}
}
/**
* Forces the main activity to re-launch if it's unloaded.
*/
private void forceMainActivityReload()
{
PackageManager pm = getPackageManager();
Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName());
startActivity(launchIntent);
}
#Override
protected void onResume() {
super.onResume();
final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
}
}
Manifeast file
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission
android:name="MyPackageName".push.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<activity android:name="MyPackageName".push.PushHandlerActivity"/>
<receiver android:name="MyPackageName".push.CordovaGCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="MyPackageName".push" />
</intent-filter>
</receiver>
<service android:name="MyPackageName".push.GCMIntentService" />
I'm creating a location based app which gets the current location of the user every 'x' minutes in the background, for that I'm planning to use PeriodicWorkRequest using WorkManager API.
But the thing is the minimum time for repeat interval is 15 minutes, so I thought I could use this Codelab (which uses Broadcast receiver in the pending intent) to get user's location updates, Which of the methods should I be using?
I'm planning to get Latitude and longitude coordinates from an API and create a Geofence with that latitude and longitude using Periodic Work Request Every 15 minutes in the Background, But the thing is I have Android Oreo and my Geofence does not seem to be triggered or I do not get any notification when my App is killed, I only get when app is in background or in the foreground even after I'm using Broadcast receiver for the pending intent in creating Geofence.
Also my periodic work does not seem to run as expected, because I Have put a test notification in the work and it gets executed only when I open the app for the first time and does not work after 15 minutes as it is expected to work, My Main Goal is to Create a periodic work request which runs every 15 minutes in the background, downloads location data from API and creates a geofence with that location, but since my work manager and geofence is not working properly I'm not able to achieve desired results. Any help would be appreciated. Here is the Code I'm using.
MainActivity:
package com.example.rohit.geofencefundo;
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
private static final int LOC_PERM_REQ_CODE = 1;
//meters
private static final int GEOFENCE_RADIUS = 150;
//in milli seconds
private static final int GEOFENCE_EXPIRATION = 600000;
LocationManager locationManager;
android.location.LocationListener locationListener;
LatLng userLocation;
Data data;
private GoogleMap mMap;
private GeofencingClient geofencingClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* geofencingClient = LocationServices.getGeofencingClient(this);
showCurrentLocationOnMap();*/
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.g_map);
mapFragment.getMapAsync(this);
geofencingClient = LocationServices.getGeofencingClient(this);
//addresses = geocoder.getFromLocation(lat, lng, 1);
data = new Data.Builder()
.putString(BackgroundWork.EXTRA_TITLE, "Message from Activity!")
.putString(BackgroundWork.EXTRA_TEXT, "Hi! I have come from activity.")
.build();
}
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
mMap.getUiSettings().setZoomControlsEnabled(true);
mMap.setMinZoomPreference(15);
showCurrentLocationOnMap();
//locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
PeriodicWorkRequest.Builder photoCheckBuilder =
new PeriodicWorkRequest.Builder(BackgroundWork.class, 15,
TimeUnit.MINUTES)
.addTag("notifWorker");
// ...if you want, you can apply constraints to the builder here...
// Create the actual work object:
PeriodicWorkRequest photoCheckWork = photoCheckBuilder.setInputData(data).build();
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(photoCheckWork);
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng latLng) {
addLocationAlert(latLng.latitude, latLng.longitude);
}
});
}
#SuppressLint("MissingPermission")
private void showCurrentLocationOnMap() {
if (isLocationAccessPermitted()) {
requestLocationAccessPermission();
} else if (mMap != null) {
mMap.setMyLocationEnabled(true);
}
}
/*public void addFence(View view){
addLocationAlert(28.360596,75.5863917); //28.360596 75.5863917 budh bhavan
addLocationAlert( 28.3603219,75.5868534); //ram marg
}*/
/*#SuppressLint("MissingPermission")
private void showCurrentLocationOnMap() {
if (isLocationAccessPermitted()) {
requestLocationAccessPermission();
}
}*/
private boolean isLocationAccessPermitted() {
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return true;
} else {
return false;
}
}
private void requestLocationAccessPermission() {
ActivityCompat.requestPermissions(this,
new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
LOC_PERM_REQ_CODE);
}
#SuppressLint("MissingPermission")
private void addLocationAlert(double lat, double lng) {
if (isLocationAccessPermitted()) {
requestLocationAccessPermission();
} else {
String key = "" + lat + "-" + lng;
Geofence geofence = getGeofence(lat, lng, key);
/* geofencingClient.addGeofences(getGeofencingRequest(geofence),
getGeofencePendingIntent())
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Toast.makeText(MainActivity.this,
"Location alter has been added",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this,
"Location alter could not be added",
Toast.LENGTH_SHORT).show();
}
}
});*/
geofencingClient.addGeofences(getGeofencingRequest(geofence),
getGeofencePendingIntent())
.addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(MainActivity.this, "added", Toast.LENGTH_SHORT).show();
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
});
}
}
private void removeLocationAlert() {
if (isLocationAccessPermitted()) {
requestLocationAccessPermission();
} else {
geofencingClient.removeGeofences(getGeofencePendingIntent())
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Toast.makeText(MainActivity.this,
"Location alters have been removed",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this,
"Location alters could not be removed",
Toast.LENGTH_SHORT).show();
}
}
});
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions,
#NonNull int[] grantResults) {
switch (requestCode) {
case LOC_PERM_REQ_CODE: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showCurrentLocationOnMap();
Toast.makeText(MainActivity.this,
"Location access permission granted, you try " +
"add or remove location allerts",
Toast.LENGTH_SHORT).show();
}
return;
}
}
}
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(this, LocationAlertBroadcastReceiver.class);
return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private GeofencingRequest getGeofencingRequest(Geofence geofence) {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL);
builder.addGeofence(geofence);
return builder.build();
}
private Geofence getGeofence(double lat, double lang, String key) {
return new Geofence.Builder()
.setRequestId(key)
.setCircularRegion(lat, lang, GEOFENCE_RADIUS)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_DWELL)
.setLoiteringDelay(10000)
.build();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.remove_loc_alert:
removeLocationAlert();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
GeofenceTransitionsJobIntentService:
package com.example.rohit.geofencefundo;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.location.Address;
import android.location.Geocoder;
import android.os.Build;
import android.support.v4.app.JobIntentService;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Random;
public class GeofenceTransitionsJobIntentService extends JobIntentService {
private static final int JOB_ID = 573;
private static final String TAG = "GeofenceTransitionsIS";
private static final String CHANNEL_ID = "channel_01";
/**
* Convenience method for enqueuing work in to this service.
*/
public static void enqueueWork(Context context, Intent intent) {
enqueueWork(context, GeofenceTransitionsJobIntentService.class, JOB_ID, intent);
}
/**
* 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 onHandleWork(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_DWELL) {
// 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 = getGeofenceTransitionInfo(
triggeringGeofences);
String transitionType = getTransitionString(geofenceTransition);
sendNotification(transitionType, geofenceTransitionDetails);
sendNotification2(geofenceTransitionDetails);
Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition));
}
}
private String getGeofenceTransitionInfo(List<Geofence> triggeringGeofences) {
ArrayList<String> locationNames = new ArrayList<>();
for (Geofence geofence : triggeringGeofences) {
locationNames.add(getLocationName(geofence.getRequestId()));
}
String triggeringLocationsString = TextUtils.join(", ", locationNames);
return triggeringLocationsString;
}
private String getLocationNameGeocoder(double lat, double lng) {
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
List<Address> addresses = null;
try {
addresses = geocoder.getFromLocation(lat,lng, 1);
} catch (Exception ioException) {
Log.e("", "Error in getting location name for the location");
}
if (addresses == null || addresses.size() == 0) {
Log.d("", "no location name");
return null;
} else {
Address address = addresses.get(0);
ArrayList<String> addressInfo = new ArrayList<>();
for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
addressInfo.add(address.getAddressLine(i));
}
return TextUtils.join(System.getProperty("line.separator"), addressInfo);
}
}
private String getLocationName(String key) {
String[] strs = key.split("-");
String locationName = null;
if (strs != null && strs.length == 2) {
double lat = Double.parseDouble(strs[0]);
double lng = Double.parseDouble(strs[1]);
locationName = getLocationNameGeocoder(lat, lng);
}
if (locationName != null) {
return locationName;
} else {
return key;
}
}
private String getGeofenceTransitionDetails(
int geofenceTransition,
List<Geofence> triggeringGeofences) {
String geofenceTransitionString = getTransitionString(geofenceTransition);
// Get the Ids of each geofence that was triggered.
ArrayList<String> triggeringGeofencesIdsList = new ArrayList<>();
for (Geofence geofence : triggeringGeofences) {
triggeringGeofencesIdsList.add(geofence.getRequestId());
}
String triggeringGeofencesIdsString = TextUtils.join(", ", triggeringGeofencesIdsList);
return geofenceTransitionString + ": " + triggeringGeofencesIdsString;
}
private void sendNotification(String locTransitionType, String locationDetails) {
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
String NOTIFICATION_CHANNEL_ID = "rohit901";
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.O){
#SuppressLint("WrongConstant") NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,"My Notification",NotificationManager.IMPORTANCE_MAX);
notificationChannel.setDescription("rohit901 channel for app test FCM");
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setVibrationPattern(new long[]{0,1000,500,1000});
notificationChannel.enableVibration(true);
notificationManager.createNotificationChannel(notificationChannel);
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this,NOTIFICATION_CHANNEL_ID);
notificationBuilder.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(android.support.compat.R.drawable.notification_icon_background)
.setTicker("Hearty365")
.setContentTitle(locTransitionType)
.setContentText(locationDetails)
.setContentInfo("info");
Random random = new Random();
int m = (int) ((new Date().getTime() / 1000L) % Integer.MAX_VALUE);
m += random.nextInt(100) + 1;
notificationManager.notify(m,notificationBuilder.build());
}
private String getTransitionString(int transitionType) {
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
return getString(R.string.geofence_transition_entered);
case Geofence.GEOFENCE_TRANSITION_DWELL:
return "dwell at location";
case Geofence.GEOFENCE_TRANSITION_EXIT:
return getString(R.string.geofence_transition_exited);
default:
return getString(R.string.unknown_geofence_transition);
}
}
}
Background Work (Worker Class):
package com.example.rohit.geofencefundo;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Random;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class BackgroundWork extends Worker {
public static final String EXTRA_TITLE = "title";
public static final String EXTRA_TEXT = "text";
public static final String EXTRA_OUTPUT_MESSAGE = "output_message";
JSONObject jsonPart;
String result;
private Context mContext;
public BackgroundWork(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
mContext = context;
}
#NonNull
#Override
public Worker.Result doWork() {
//do work and shit
Log.d("work901","work and shit");
String title = getInputData().getString(EXTRA_TITLE);
String text = getInputData().getString(EXTRA_TEXT);
Log.d("work901tit","title from main is "+title);
Data output = new Data.Builder()
.putString(EXTRA_OUTPUT_MESSAGE, "I have come from MyWorker!")
.build();
sendNotification(title,text);
new JsonTask().execute("https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2018-10-20&endtime=2018-10-21&latitude=33.9370804&longitude=135.8284085&maxradiuskm=5000");
setOutputData(output);
return Result.SUCCESS;
}
private void sendNotification(String title, String text) {
NotificationManager notificationManager = (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
String NOTIFICATION_CHANNEL_ID = "rohit901";
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.O){
#SuppressLint("WrongConstant") NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,"My Notification",NotificationManager.IMPORTANCE_MAX);
notificationChannel.setDescription("rohit901 channel for app test FCM");
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setVibrationPattern(new long[]{0,1000,500,1000});
notificationChannel.enableVibration(true);
notificationManager.createNotificationChannel(notificationChannel);
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(mContext,NOTIFICATION_CHANNEL_ID);
notificationBuilder.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(android.support.compat.R.drawable.notification_icon_background)
.setTicker("Hearty365")
.setContentTitle(title)
.setContentText(text)
.setContentInfo("info");
Random random = new Random();
int m = (int) ((new Date().getTime() / 1000L) % Integer.MAX_VALUE);
m += random.nextInt(100) + 1;
notificationManager.notify(m,notificationBuilder.build());
}
public class JsonTask extends AsyncTask<String,Void,String> {
#Override
protected void onPreExecute() {
// SHOW THE SPINNER WHILE LOADING FEEDS
//pd.show();
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
//Toast.makeText(FeedBackActivity.this, "DOne", Toast.LENGTH_SHORT).show();
Log.d("rohit901",s);
result = s;
try {
JSONObject jsonObject = new JSONObject(result);
String features = jsonObject.getString("features"); //array of features
//JSONObject jsonObject2 = new JSONObject(features);
//String geometry = jsonObject2.getString("geometry"); //array of geometry
//JSONObject jsonObject3 = new JSONObject(geometry);
//String coordinates = jsonObject3.getString("coordinates");
JSONArray arr = new JSONArray(features);
JSONArray locArr;
Log.d("work901Array",arr.toString());
for(int i =0;i<arr.length();i++) {
jsonPart = arr.getJSONObject(i);
//jsonPart2 =
locArr = new JSONArray(jsonPart.getJSONObject("geometry").getString("coordinates"));
Log.d("geo901","Longitude: "+locArr.getString(0)+", Latitude: "+locArr.getString(1));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
protected String doInBackground(String... urls) {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL(urls[0]);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
InputStream stream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line+"\n");
Log.d("Response: ", "> " + line); //here u ll get whole response...... :-)
}
return buffer.toString();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
}
Rest of my code is here: GitHub
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent=new Intent(context,ActivityExpenseDetails.class);
intent.putExtra("employee_name",expenseArrayList.get(position).getEmployee_name());
intent.putExtra("expense_type",expenseArrayList.get(position).getExpense_type());
intent.putExtra("amount",expenseArrayList.get(position).getAmount());
intent.putExtra("description",expenseArrayList.get(position).getDescription());
context.startActivity(intent);
}
});
I have this service which send my location to server and get some data from it.
package com.speed00.service;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.widget.Toast;
import com.google.gson.Gson;
import com.speed00.R;
import com.speed00.constant.AppConstant;
import com.speed00.helper.FunctionHelper;
import com.speed00.model.achivment.StepHelper;
import com.speed00.model.ride.RideResultResponse;
import com.speed00.model.ride.UpdateLocationData;
import com.speed00.model.ride.UpdateLocationResponse;
import com.speed00.prefrences.MyPreferences;
import com.speed00.prefrences.MySharedPreference;
import com.speed00.retrofit.ApiUtils;
import com.speed00.stepdetucter.StepDetector;
import com.speed00.stepdetucter.StepListener;
import com.speed00.ui.activity.HomeActivity;
import com.speed00.utils.Logger;
import com.speed00.utils.MySensorEventListener;
import com.speed00.utils.NetworkConnection;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MyService extends Service implements StepListener, SensorEventListener {
// constant
// run on another Thread to avoid crash
private Handler mHandler = new Handler();
// timer handling
private Timer mTimer = null;
private Double locationRange = 1.0;
private String oldDegree;
private boolean isServiceRunning;
private int numSteps = 0;
private final int FINAL_STEP = 15;
private SensorManager sensorManager;
private Sensor accel;
String speed_received = "0";
private boolean pedoMetterHasStarted = false;
String pLatitude;
String pLongitude;
private SensorEventListener eListener;
private Sensor accelerometer, magFieldSensor;
private StepDetector simpleStepDetector;
#Override
public IBinder onBind(Intent intent) {
return null;
}
private BroadcastReceiver message = new BroadcastReceiver() {
#Override
public void onReceive(Context context, final Intent intent) {
// TODO Auto-generated method stub
new Handler().post(new Runnable() {
#Override
public void run() {
speed_received = intent.getStringExtra("current speed");
Logger.e("speed__recv" + speed_received);
if (speed_received != null) {
// setSpeedValue(speed_received);//this method is calling to set speed.
//This method is calling to validate Timer For Ride
if (speed_received.equalsIgnoreCase("0")) {
if (!pedoMetterHasStarted) {
numSteps = 0;
statPedoMeter();
}
}
//Calculating distance value
String latitude = MySharedPreference.getInstance(MyService.this).getLatitude();
String longitude = MySharedPreference.getInstance(MyService.this).getLongitude();
if (TextUtils.isEmpty(pLatitude)) {
pLatitude = latitude;
pLongitude = longitude;
} else {
/*
* if distance is more then 2 km then its calculatiog.
*
* */
Location pLocation = new Location("P");
pLocation.setLongitude(Double.parseDouble(pLatitude));
pLocation.setLongitude(Double.parseDouble(pLongitude));
Location nLocation = new Location("N");
nLocation.setLongitude(Double.parseDouble(latitude));
nLocation.setLongitude(Double.parseDouble(longitude));
Logger.e("distance----" + pLocation.distanceTo(nLocation));
if (pLocation.distanceTo(nLocation) >= 10) {
pLatitude = latitude;
pLongitude = longitude;
}
}
}
}
});
}
};
#Override
public void onCreate() {
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
eListener = new MySensorEventListener(this, sensorManager);//for direction
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
//callback sensor of ui
sensorManager.registerListener(eListener, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(eListener, magFieldSensor, SensorManager.SENSOR_DELAY_NORMAL);
assert sensorManager != null;
accel = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
simpleStepDetector = new StepDetector();
simpleStepDetector.registerListener(this);
Toast.makeText(getApplicationContext(), "Service Started", Toast.LENGTH_SHORT).show();
isServiceRunning = true;
// Toast.makeText(getApplicationContext(), "service started", Toast.LENGTH_SHORT).show();
// cancel if already existed
if (mTimer != null) {
mTimer.cancel();
} else {
// recreate new
mTimer = new Timer();
}
// schedule task
// new Timer().scheduleAtFixedRate(new TimeDisplayTimerTask(), 100, NOTIFY_INTERVAL);
runLoop();
LocalBroadcastManager.getInstance(this).registerReceiver(message,
new IntentFilter("send"));
}
private void runLoop() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
updateDriverLocation();
if (isServiceRunning)
runLoop();
}
}, 1000);
}
/*
*
* This method is calling to update driver location
* */
private void updateDriverLocation() {
//Get all user details data.
String userId = MySharedPreference.getInstance(getApplicationContext()).getDriverId();
String latitudeData = MySharedPreference.getInstance(getApplicationContext()).getLatitude();
String longitudeData = MySharedPreference.getInstance(getApplicationContext()).getLongitude();
String speedData = MySharedPreference.getInstance(getApplicationContext()).getSpeedData();//how to roinf
Logger.e("SERVER " + latitudeData + " " + longitudeData);
Logger.e("SPEED " + speedData);
String directionValue = MySharedPreference.getInstance(getApplicationContext()).getDirectionValue();
String speeding = MySharedPreference.getInstance(getApplicationContext()).getSpeeding();
String language = MyPreferences.newInstance(getApplicationContext()).getLanguage();//send speeding data to bakend.
String directionText = MySharedPreference.getInstance(getApplicationContext()).getDirectionText();
long driveTime = FunctionHelper.getDifference(MySharedPreference.getInstance(getApplicationContext()).getStartTime());
String validateUTurn = validateUTurn();
// Logger.e("update_request--" + request);
ApiUtils.getAPIService().updateDriverLocation(validateUTurn, directionText, speeding, "" + driveTime, language, userId,
AppConstant.START_RIDE, speedData, directionValue, latitudeData, longitudeData).enqueue(new Callback<UpdateLocationResponse>() {
#Override
public void onResponse(Call<UpdateLocationResponse> call, Response<UpdateLocationResponse> response) {
if (response.isSuccessful()) {
UpdateLocationResponse updateLocationResponse = response.body();
if (updateLocationResponse.getErrorCode().equalsIgnoreCase(AppConstant.ERROR_CODE)) {
if (updateLocationResponse.getData().size() > 0) {
UpdateLocationData updateLocationData = updateLocationResponse.getData().get(0);
String eventData = FunctionHelper.validateData(updateLocationData);
broadCastEventData(eventData, updateLocationResponse.getErrorCode());
broadCastResponseData(updateLocationResponse);
}
}
}
}
#Override
public void onFailure(Call<UpdateLocationResponse> call, Throwable t) {
}
});
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
/*
*
*This method is calling to validateUTurn.
*
*
* */
private String validateUTurn() {
String currentDegree = MySharedPreference.getInstance(this).getDirectionValue();
String uTurnStatus = "0";
if (TextUtils.isEmpty(oldDegree)) {
oldDegree = currentDegree;
uTurnStatus = "0";
return uTurnStatus;
} else {
if (oldDegree == "0.0") {
oldDegree = currentDegree;
}
if (Float.parseFloat(oldDegree) <= Float.parseFloat(currentDegree)) {
uTurnStatus = "1";
} else {
uTurnStatus = "0";
}
if (Float.parseFloat(oldDegree) == 360 || Float.parseFloat(oldDegree) > 270) {
if (Float.parseFloat(currentDegree) > 0 && Float.parseFloat(oldDegree) <= 90) {
uTurnStatus = "1";
}
}
oldDegree = currentDegree;
return uTurnStatus;
}
}
public synchronized void broadCastEventData(String eventData, String errorCode) {
Intent intent = new Intent("event_broad_cast");
intent.putExtra("data", eventData);
intent.putExtra("error", errorCode);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
/*
* This method is calling to broadcast event data
* */
/*
* This method is calling to broadcast event data
* */
public synchronized void broadCastResponseData(UpdateLocationResponse updateLocationData) {
Intent intent = new Intent("location_update");
Bundle args = new Bundle();
args.putSerializable("data", (Serializable) updateLocationData);
intent.putExtra("myvalue", args);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
#Override
public void onDestroy() {
super.onDestroy();
isServiceRunning = false;
LocalBroadcastManager.getInstance(this).unregisterReceiver(message);
// stopPedoMeter();
}
#Override
public void step(long timeNs) {
if (numSteps < 16)
Toast.makeText(this, "" + numSteps, Toast.LENGTH_SHORT).show();
saveStep();
if (calculateSteps()) {
stopRideAfter25step();
stopPedoMeter();//this method is calling to stop ResultActivity
} else if (Double.parseDouble(MySharedPreference.getInstance(this).getSpeedData()) > 10) {
MySharedPreference.getInstance(this).setSteps(new ArrayList<StepHelper>());
numSteps = 0;
} else {
Logger.e("stepsss " + numSteps);
numSteps = numSteps + 1;
}
}
private void stopRideAfter25step() {
MySharedPreference.getInstance(this).setSteps(new ArrayList<StepHelper>());
String speed = MySharedPreference.getInstance(this).getSpeedData();
String direction = MySharedPreference.getInstance(this).getDirectionValue();
String latitude = MySharedPreference.getInstance(this).getLatitude();
String longitude = MySharedPreference.getInstance(this).getLongitude();
String driverId = MySharedPreference.getInstance(this).getDriverId();
String language = MyPreferences.newInstance(this).getLanguage();
String speeding = MySharedPreference.getInstance(this).getSpeeding();
long driveTime = FunctionHelper.getDifference(MySharedPreference.getInstance(this).getStartTime());
//validate internet connection
if (NetworkConnection.isConnection(this)) {
stopRide("" + driveTime, language, speeding, driverId, latitude, longitude, AppConstant.STOP_RIDE, speed, direction);
} else {
Toast.makeText(this, getString(R.string.valid_please_check_network_connection), Toast.LENGTH_LONG).show();
}
}
public void stopRide(final String driveTime, final String languageKey, final String speedLimit, final String driverId, final String latitude, final String longitude, String rideStatus, final String speed, final String direction) {
String[] param = {driveTime, languageKey, speedLimit, driverId, speed, direction, latitude, longitude};
new stopLoc(this).execute(param);
}
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
simpleStepDetector.updateAccel(event.timestamp, event.values[0], event.values[1], event.values[2]);
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
private static class stopLoc extends AsyncTask<String[], Void, Void> {
Context mContext;
public stopLoc(Context mContext) {
this.mContext = mContext;
}
#Override
protected Void doInBackground(String[]... strings) {
Logger.e("stepsss stopingggg");
String driveTime = strings[0][0];
String languageKey = strings[0][1];
String speedLimit = strings[0][2];
String driverId = strings[0][3];
String speed = strings[0][4];
String direction = strings[0][5];
String latitude = strings[0][6];
String longitude = strings[0][7];
ApiUtils.getAPIService().stopTrip(driveTime, languageKey, speedLimit, driverId, AppConstant.STOP_RIDE, speed, direction, latitude, longitude).enqueue(new Callback<RideResultResponse>() {
#Override
public void onResponse(Call<RideResultResponse> call, Response<RideResultResponse> response) {
Logger.e("response----" + new Gson().toJson(response.body()));
if (response.isSuccessful()) {
String errorCode = response.body().getErrorCode();
if (errorCode.equalsIgnoreCase(AppConstant.ERROR_CODE)) {
tripSuccessData(response.body(), mContext);
} else {
Toast.makeText(mContext, "" + mContext.getString(R.string.text_some_erroroccurs), Toast.LENGTH_SHORT).show();
}
}
}
#Override
public void onFailure(Call<RideResultResponse> call, Throwable t) {
Toast.makeText(mContext, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
return null;
}
}
public static void tripSuccessData(Object data, Context context) {
RideResultResponse resultResponse = (RideResultResponse) data;
if (resultResponse.getErrorCode().equalsIgnoreCase(AppConstant.ERROR_CODE)) {
FunctionHelper.enableUserIntraction();
HomeActivity.bSpeedStatus = false;
Intent intent = new Intent("trip_ended");
intent.putExtra("trip_info", resultResponse.getData().get(0));
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
((MyService) context).stopSelf();
} else {
Toast.makeText(context, resultResponse.getErrorMsg(), Toast.LENGTH_SHORT).show();
}
}
private boolean calculateSteps() {
ArrayList<StepHelper> arrayList = MySharedPreference.getInstance(this).getSteps();
ArrayList<StepHelper> newList = new ArrayList<>();
for (int i = 0; i < arrayList.size(); i++) {
long seconds = FunctionHelper.getDifference(arrayList.get(i).getTime());
if ((int) seconds < 180) {
newList.add(arrayList.get(i));
}
}
if (numSteps - newList.get(0).getSteps() >= FINAL_STEP) {
return true;
} else {
MySharedPreference.getInstance(this).setSteps(newList);
return false;
}
}
private void saveStep() {
ArrayList<StepHelper> arrayList = MySharedPreference.getInstance(this).getSteps();
if (arrayList == null) {
arrayList = new ArrayList<>();
}
arrayList.add(new StepHelper(FunctionHelper.getCurrentDateWitTime(), numSteps));
MySharedPreference.getInstance(this).setSteps(arrayList);
}
private void statPedoMeter() {
// Toast.makeText(this, "start count", Toast.LENGTH_SHORT).show();
pedoMetterHasStarted = true;
MySharedPreference.getInstance(this).setStartPedoMeterTime(FunctionHelper.getCurrentDateWitTime());//store current time
if (sensorManager != null)
sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_NORMAL);
}
/*
* This method is calling to stop pedo meter
*
* */
private void stopPedoMeter() {
numSteps = 0;
pedoMetterHasStarted = false;
sensorManager.unregisterListener(this, accelerometer);
sensorManager.unregisterListener(this, accel);
sensorManager.unregisterListener(this, magFieldSensor);
sensorManager = null;
}
class TimeDisplayTimerTask extends TimerTask {
#Override
public void run() {
// run on another thread
mHandler.post(new Runnable() {
#Override
public void run() {
Logger.e("MyService --- " + "isRuning");
if (HomeActivity.bSpeedStatus) {
updateDriverLocation();
}
}
});
}
}
}
My app starts and use only 170 MB , but after starting this service , it becomes 220 MB and after finishing it, it still 220 and if i open it again , ram become 240 and if i finish it still 240 and if i open it again it become 260 and so on until app crash
what's the wrong in the service ?
i make sure that it's stopped but don't know why ram not reduced after this service is finished
Try this approache, don't put the stopSelf() inside a static method. Instead, you should call startService again and add an action to the Intent.
So this is how an example actionable service should look like:
public class ExampleService
extends Service {
public static final String START_SERVICE_ACTION = "com.example.action.START_SERVICE";
public static final String STOP_SERVICE_ACTION = "com.example.action.STOP_SERVICE";
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String intentAction = intent.getAction();
switch (intentAction) {
case START_SERVICE_ACTION:
//
break;
case STOP_SERVICE_ACTION:
stopSelf();
break;
}
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
// Used only in case if services are bound (Bound Services).
return null;
}
}
Interact with the Service from somewhere:
Start with it like this:
Intent service = new Intent(context, ExampleService.class);
service.setAction(NotificationService.START_SERVICE_ACTION);
startService(service);
Stop it like this:
Intent service = new Intent(context, ExampleService.class);
service.setAction(NotificationService.STOP_SERVICE_ACTION);
startService(service);
You don't really need to pass the START_SERVICE_ACTION in this example, just added it to better understand the design.
I saw some questions like this, but none of them solved my problem.
I'm starting a background service through an AlarmManager. Everytime the service starts, it checks if it has been disabled in SharedPreferences and, if not, it reschedule a new instance of itself and goes on, following these alternative paths:
if the user wants to use GPS, it waits for user's position and uses
it to call a REST endpoint;
if the user does not want to use GPS, it uses the position stored in prefs.
The result of the HTTP call (timeout: 30 seconds) is a JSONObject, which generates 0-n notifications (depending on how many "close objects" it finds).
My problem is: notifications, even if canceled by the user (sliding on them or opening them), often reappears, as if they were never shown. It should never happen, because the web service receives a list of excluded object ids that are updated every time.
Here the code:
ScannerService.java
package com.kiulomb.itascanner.service;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import android.media.RingtoneManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import com.kiulomb.itascanner.R;
import com.kiulomb.itascanner.network.HTTPRequestManager;
import com.kiulomb.itascanner.network.HTTPResponseListener;
import com.kiulomb.itascanner.network.URLs;
import com.kiulomb.itascanner.pref.FilterPreferencesManager;
import com.kiulomb.itascanner.pref.NotificationsHistoryManager;
import com.kiulomb.itascanner.pref.PrefConstants;
import com.kiulomb.itascanner.utils.Haversine;
import com.kiulomb.itascanner.utils.MyConfiguration;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
public class ScannerService extends Service {
private static final String TAG = ScannerService.class.getSimpleName();
private boolean locationFound = false;
private boolean withoutLocation = false;
private LocationManager mLocationManager = null;
private final Timer myTimer = new Timer();
private final long TIMEOUT = 20000;
TimerTask myTask = new TimerTask() {
public void run() {
try {
Log.i(TAG, "Timeout is over, trying to stop service (location found? " + locationFound + ")");
if (!locationFound) {
stopSelf();
}
} catch (Exception e) {
Log.e(TAG, "Could not stop service after time: " + e.getMessage());
}
}
};
private LocationListener[] mLocationListeners = new LocationListener[] {
new LocationListener(LocationManager.GPS_PROVIDER),
new LocationListener(LocationManager.NETWORK_PROVIDER)
};
private boolean alreadySearching = false;
private class LocationListener implements android.location.LocationListener {
Location mLastLocation;
LocationListener(String provider) {
Log.i(TAG, "LocationListener is " + provider);
mLastLocation = new Location(provider);
}
#Override
public void onLocationChanged(final Location location) {
Log.i(TAG, "onLocationChanged: " + location);
if (withoutLocation) {
return;
}
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
if (location != null) {
if (isConnected) {
mLastLocation.set(location);
locationFound = true;
Log.i(TAG, "already searching? " + alreadySearching);
if (!alreadySearching) {
findClosest(location.getLatitude(), location.getLongitude());
}
alreadySearching = true;
} else {
Log.e(TAG, "no connectivity, ending service");
stopSelf();
}
} else {
Log.e(TAG, "no position, ending service");
stopSelf();
}
}
#Override
public void onProviderDisabled(String provider) {
Log.i(TAG, "onProviderDisabled: " + provider);
}
#Override
public void onProviderEnabled(String provider) {
Log.i(TAG, "onProviderEnabled: " + provider);
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.i(TAG, "onStatusChanged: " + provider);
}
}
private void initializeLocationManager() {
Log.d(TAG, "initializeLocationManager");
if (mLocationManager == null) {
mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
}
}
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
// super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
#Override
public void onCreate() {
Log.d(TAG, "onCreate");
SharedPreferences pref = getSharedPreferences(PrefConstants.PREF_APP_FILE, MODE_PRIVATE);
if (pref.getBoolean(PrefConstants.PREF_APP_SERVICE_ENABLED, PrefConstants.PREF_APP_SERVICE_ENABLED_DEFAULT)) {
Intent intent = new Intent(ScannerService.this, ScannerService.class);
PendingIntent pintent = PendingIntent.getService(ScannerService.this, 0, intent, 0);
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Calendar cal = Calendar.getInstance();
alarm.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis() + 60000, pintent); // or setExact() // TODO custom time
// alarm.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), 60000, pintent);
if (!pref.getBoolean(PrefConstants.PREF_APP_SERVICE_CUSTOMCENTER, PrefConstants.PREF_APP_SERVICE_CUSTOMCENTER_DEFAULT)) {
// use GPS
initializeLocationManager();
try {
mLocationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
MyConfiguration.LOCATION_INTERVAL,
MyConfiguration.LOCATION_DISTANCE,
mLocationListeners[1]);
} catch (SecurityException ex) {
Log.e(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.e(TAG, "network provider does not exist, " + ex.getMessage());
}
try {
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
MyConfiguration.LOCATION_INTERVAL,
MyConfiguration.LOCATION_DISTANCE,
mLocationListeners[0]);
} catch (SecurityException ex) {
Log.e(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.e(TAG, "gps provider does not exist " + ex.getMessage());
}
} else {
withoutLocation = true;
// do not use GPS
String[] savedNotifCenter = pref.getString(PrefConstants.PREF_APP_SERVICE_CENTER, PrefConstants.PREF_APP_SERVICE_CENTER_DEFAULT).split(",");
double savedLat = Double.parseDouble(savedNotifCenter[0]);
double savedLng = Double.parseDouble(savedNotifCenter[1]);
locationFound = true; // prevent the service from stopping
findClosest(savedLat, savedLng);
}
} else {
stopSelf();
return;
}
/*if (isForeground(getPackageName())) {
Log.i(getClass().getSimpleName(), "application is in foreground, stopping service");
stopSelf();
return;
}*/
myTimer.schedule(myTask, TIMEOUT);
}
public boolean isForeground(String myPackage) {
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> runningTaskInfo = manager.getRunningTasks(1);
ComponentName componentInfo = runningTaskInfo.get(0).topActivity;
return componentInfo.getPackageName().equals(myPackage);
}
#Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
if (mLocationManager != null) {
for (LocationListener mLocationListener : mLocationListeners) {
try {
mLocationManager.removeUpdates(mLocationListener);
} catch (SecurityException se) {
Log.e(TAG, "security exception", se);
} catch (Exception ex) {
Log.e(TAG, "fail to remove location listeners, ignore", ex);
}
}
}
}
private void findClosest(final double lat, final double lng) {
new Thread(new Runnable() {
#Override
public void run() {
String url = URLs.buildURL(URLs.NOTIFICATIONS);
url += "?lat=" + lat;
url += "&lng=" + lng;
final SharedPreferences pref = getSharedPreferences(PrefConstants.PREF_APP_FILE, MODE_PRIVATE);
if (pref.contains(PrefConstants.PREF_APP_SERVICE_RADIUS)) {
url += "&radius=" + pref.getInt(PrefConstants.PREF_APP_SERVICE_RADIUS, PrefConstants.PREF_APP_SERVICE_RADIUS_DEFAULT);
}
url += "&limit=" + PrefConstants.PREF_APP_MAP_LIMIT_DEFAULT;
if (pref.contains(PrefConstants.PREF_APP_SERVICE_IV)) {
url += "&iv=" + pref.getInt(PrefConstants.PREF_APP_SERVICE_IV, PrefConstants.PREF_APP_SERVICE_IV_DEFAULT);
}
String exclusionsNumbers = getExcludedNumbersParam();
if (exclusionsNumbers.length() > 0) {
url += "&exNum=" + exclusionsNumbers;
}
final NotificationsHistoryManager notificationsHistoryManager = new NotificationsHistoryManager(ScannerService.this);
final List<Long> excludedIds = notificationsHistoryManager.getAlreadyFoundObjects();
String exclusionsIds = getExcludedIdsParam(excludedIds);
if (exclusionsIds.length() > 0) {
url += "&exId=" + exclusionsIds;
}
/*final long lastId = pref.getLong(PrefConstants.PREF_SERVICE_LAST_ID, 0L);
url += "&li=" + lastId;*/
final Context context = ScannerService.this;
HTTPRequestManager requestManager = new HTTPRequestManager(context, url, true, null, new HTTPResponseListener() {
#Override
public void onSuccess(JSONObject response) {
try {
JSONArray responseArray = response.getJSONArray("objects");
final String foundString = getString(R.string.found);
final String inCityString = getString(R.string.in_city);
final String expiringString = getString(R.string.expiring);
final DateFormat sdf = SimpleDateFormat.getTimeInstance();
final Resources res = getResources();
final String packageName = getPackageName();
final String mapsApiKey = getString(R.string.google_maps_key);
final boolean notifClickAutoCancel = pref.getBoolean(PrefConstants.PREF_APP_SERVICE_NOTIFCANCEL, PrefConstants.PREF_APP_SERVICE_NOTIFCANCEL_DEFAULT);
final boolean notifExpiredAutoCancel = pref.getBoolean(PrefConstants.PREF_APP_SERVICE_NOTIFCANCELEXPIRED, PrefConstants.PREF_APP_SERVICE_NOTIFCANCELEXPIRED_DEFAULT);
final boolean mapPicture = pref.getBoolean(PrefConstants.PREF_APP_SERVICE_MAPPICTURE, PrefConstants.PREF_APP_SERVICE_MAPPICTURE_DEFAULT);
final Locale defaultLocale = Locale.getDefault();
Calendar calendar = Calendar.getInstance();
// long maxId = lastId;
for (int i = 0; i < responseArray.length(); i++) {
try {
final MyEntity p = MyEntity.fromJSONLight(responseArray.getJSONObject(i));
// it should never happen, but notifications are shown many times :/
if (!excludedIds.contains(p.getId())) {
excludedIds.add(p.getId());
// maxId = Math.max(p.getId(), maxId);
final double iv = p.getIV();
final long expirationFixed = (p.getDisappearTime() - System.currentTimeMillis() - 2000);
final Calendar expirationTime = (Calendar) calendar.clone();
// now.add(Calendar.SECOND, (int) ((p.getDisappearTime() - System.currentTimeMillis() / 1000) - 2));
expirationTime.setTimeInMillis(expirationTime.getTimeInMillis() + expirationFixed);
final int distance = (int) Math.round(1000 * Haversine.distance(lat, lng, p.getLatitude(), p.getLongitude()));
String cityName = null;
Geocoder gcd = new Geocoder(context, defaultLocale);
List<Address> addresses = gcd.getFromLocation(p.getLatitude(), p.getLongitude(), 1);
if (addresses.size() > 0) {
cityName = addresses.get(0).getLocality();
}
final String cityNameParam = cityName;
new Thread(new Runnable() {
#Override
public void run() {
sendNotification((int) (p.getId()),
foundString + " " + p.getName() + (iv > 0 ? " " + iv + "%" : "") + (cityNameParam != null ? " " + inCityString + " " + cityNameParam : ""),
expiringString + " " + sdf.format(expirationTime.getTime()) + " - " + distance + "m" + (movesStringParam != null ? " (" + movesStringParam + ")" : ""),
p,
res,
packageName,
notifClickAutoCancel,
notifExpiredAutoCancel,
expirationFixed,
mapsApiKey,
mapPicture);
}
}).start();
}
} catch (Exception e) {
Log.e(TAG, "error", e);
}
}
notificationsHistoryManager.saveAlreadyFoundObjects(excludedIds);
stopSelf();
} catch (Exception e) {
Log.e(TAG, "error in reading JSONArray", e);
stopSelf();
}
}
#Override
public void onError(int errorCode) {
stopSelf();
}
});
RequestQueue requestQueue = Volley.newRequestQueue(context);
requestQueue.add(requestManager);
}
}).start();
}
private String getExcludedNumbersParam() {
String exclusionsNumbers = "";
List<Integer> excludedNumbers = new FilterPreferencesManager(ScannerService.this).getNotificationsExcludedNumbers();
int sizeNumbers = excludedNumbers.size();
for (int i = 0; i < sizeNumbers; i++) {
exclusionsNumbers += excludedNumbers.get(i);
if (i < sizeNumbers - 1) {
exclusionsNumbers += ",";
}
}
return exclusionsNumbers;
}
private String getExcludedIdsParam(List<Long> excludedIds) {
String exclusionsIds = "";
int sizeIds = excludedIds.size();
for (int i = 0; i < sizeIds; i++) {
exclusionsIds += excludedIds.get(i);
if (i < sizeIds - 1) {
exclusionsIds += ",";
}
}
return exclusionsIds;
}
private Locale locale = Locale.getDefault();
private void sendNotification(final int notificationId,
final String title,
final String message,
final MyEntity entity,
final Resources res,
final String packageName,
final boolean autoClickCancel,
final boolean autoExpiredCancel,
final long expirationFromNow,
final String mapsApiKey,
final boolean mapPicture) {
final double entityLat = entity.getLatitude();
final double entityLng = entity.getLongitude();
Intent mapIntent = null;
try {
String urlAddress = "http://maps.google.com/maps?q=" + entityLat + "," + entityLng;
mapIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlAddress));
} catch (Exception e) {
Log.e(TAG, "error in notification intent preparation", e);
}
PendingIntent pendingIntent = PendingIntent.getActivity(ScannerService.this, 0, mapIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
int drawable = res.getIdentifier("entity" + String.format(locale, "%04d", entity.getNumber()) + "big", "drawable", packageName);
final NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(ScannerService.this)
.setSmallIcon(drawable)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(autoClickCancel)
.setSound(defaultSoundUri)
.setPriority(Notification.PRIORITY_HIGH)
.setLights(ContextCompat.getColor(ScannerService.this, R.color.colorPrimary), 500, 2000);
if (mapPicture) {
String imageUrl = "https://maps.googleapis.com/maps/api/staticmap"
+ "?center=" + entityLat + "," + entityLng
+ "&zoom=14"
+ "&scale=false"
+ "&size=450x275"
+ "&maptype=roadmap"
+ "&key=" + mapsApiKey
+ "&format=jpg"
+ "&visual_refresh=true";
Log.i(getClass().getSimpleName(), "generated url for notification image: " + imageUrl);
Bitmap bmURL = getBitmapFromURL(imageUrl);
if (bmURL != null) {
notificationBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bmURL));
}
}
if (mapIntent != null) {
notificationBuilder.setContentIntent(pendingIntent);
}
if (autoExpiredCancel) {
Log.i(getClass().getSimpleName(), "setting notification timer for expiration, id: " + notificationId + ", expiring in " + expirationFromNow + "ms");
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
Log.i(getClass().getSimpleName(), "canceling notification expired, id: " + notificationId);
notificationManager.cancel(notificationId);
}
}, expirationFromNow);
}
}
// }
private Bitmap getBitmapFromURL(String strURL) {
try {
URL url = new URL(strURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
Notifications history manager
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class NotificationsHistoryManager {
private final static String PREF_FILE = "nh";
private final static String PREF_FOUND_KEY = "f";
private SharedPreferences pref;
public NotificationsHistoryManager(Context context) {
pref = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
}
public void saveAlreadyFoundObjects(List<Long> found) {
Set<String> idsString = new HashSet<>();
int size = found.size();
for (int i = Math.max(0, size - 200); i < size; i++) {
long f = found.get(i);
idsString.add(f + "");
}
pref.edit().putStringSet(PREF_FOUND_KEY, idsString).apply();
}
public List<Long> getAlreadyFoundObjects() {
List<Long> excluded = new ArrayList<>();
for (String id : pref.getStringSet(PREF_FOUND_KEY, new HashSet<String>())) {
try {
excluded.add(Long.parseLong(id));
} catch (Exception e) {
Log.e(getClass().getSimpleName(), "error in parsing string '" + id + "' to long id: " + e.getMessage());
}
}
return excluded;
}
public void clean() {
pref.edit().clear().apply();
}
}
Note: when MainActivity is started, it checks if an instance of the service is running and, if not, it schedules a new one with an AlarmManager. I thought it was the cause of the problem, but the service, as you see, checks every time what has already been notified and skip it.
I tried changing START_STICKY to NOT_STICKY, using preferences to handle duplicate IDs, synchronizing operations... I don't know what else to try. Please, help me :) If you need any more details, just ask.
Thank you!
To share what I found... I understood what the problem is.
Take a look at NotificationsHistoryManager: it uses, to save the list of found object, a Set (the only "list" object type available in SharedPreferences), saving only the last 200 objects found (old ones expires, so it is meaningless to keep them).
THE PROBLEM IS: SET ARE NOT ORDERED LIST. The 200 objects I save are not the LAST added, because when I read them from pref (getAlreadyFoundObjects()) they are written in a set, "randomly" ordered.
I had to change the way I stored them, creating a custom string (comma separated values), to be sure they are saved in the order I want.
Hope it helps someone.
I am not able to add geofence from service class. Below is my code:
GeoFenceService:
package com.example.admin.reminderapp.services;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.Toast;
import com.example.admin.reminderapp.R;
import com.example.admin.reminderapp.database.model.Reminder;
import com.example.admin.reminderapp.geofencing.Constants;
import com.example.admin.reminderapp.geofencing.GeofenceErrorMessages;
import com.example.admin.reminderapp.geofencing.GeofenceTransitionsIntentService;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import java.util.ArrayList;
import java.util.List;
public class GeoFenceService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private Context mContext = this;
private IBinder mIBinder = new LocalBinder();
private GoogleApiClient mGoogleApiClient;
public GeoFenceService() {
Log.i(getClass().getName(), "GeoFenceService()");
}
#Override
public IBinder onBind(Intent intent) {
return mIBinder;
}
protected synchronized void buildGoogleApiClient() {
Log.i("GeoFenceService", "buildGoogleApiClient");
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
#Override
public void onCreate() {
super.onCreate();
Log.i("GeoFenceService", "onCreate");
buildGoogleApiClient();
}
#Override
public void onConnected(Bundle connectionHint) {
Log.i("GeoFenceService", "Connected to GoogleApiClient");
}
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.e("GeoFenceService", "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode());
}
#Override
public void onConnectionSuspended(int cause) {
Log.i("GeoFenceService", "Connection suspended");
}
private GeofencingRequest getGeofencingRequest(long reminderId) {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(populateGeofenceList(reminderId));
return builder.build();
}
public List<Geofence> populateGeofenceList(long reminderId) {
List<Geofence> geofenceList = new ArrayList<>();
Reminder reminder = Reminder.load(Reminder.class, reminderId);
Log.i("ReminderService", reminder.getId()+" - "+reminder.toString());
Geofence geofence = new Geofence.Builder()
.setRequestId(String.valueOf(reminderId))
.setCircularRegion(
reminder.getLat(),
reminder.getLat(),
(float) reminder.getKm() * 1000
)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
.build();
geofenceList.add(geofence);
return geofenceList;
}
private PendingIntent getGeofencePendingIntent(long reminderId) {
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
Log.i("ReminderService", intent +"");
// intent.putExtra("ReminderId", reminderId);
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public void addGeofence(long reminderId) {
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
}
try {
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
getGeofencingRequest(reminderId),
getGeofencePendingIntent(reminderId)
).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Toast.makeText(
mContext,
"Reminder Added.",
Toast.LENGTH_SHORT
).show();
} else {
String errorMessage = GeofenceErrorMessages.getErrorString(GeoFenceService.this, status.getStatusCode());
Log.e(GeoFenceService.this.getClass().getName(), errorMessage);
}
}
});
} catch (SecurityException securityException) {
Log.e(getClass().getName(), "Invalid location permission. You need to use ACCESS_FINE_LOCATION with geofences");
securityException.printStackTrace();
}
}
public void removeGeofence(long reminderId) {
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
}
try {
LocationServices.GeofencingApi.removeGeofences(
mGoogleApiClient,
getGeofencePendingIntent(reminderId)
).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Toast.makeText(
mContext,
"Reminder Removed.",
Toast.LENGTH_SHORT
).show();
} else {
String errorMessage = GeofenceErrorMessages.getErrorString(GeoFenceService.this,
status.getStatusCode());
Log.e(GeoFenceService.this.getClass().getName(), errorMessage);
}
}
});
} catch (SecurityException securityException) {
Log.e(getClass().getName(), "Invalid location permission. You need to use ACCESS_FINE_LOCATION with geofences");
securityException.printStackTrace();
}
}
#Override
public void onDestroy() {
super.onDestroy();
mGoogleApiClient.disconnect();
}
public class LocalBinder extends Binder {
public GeoFenceService getService() {
return GeoFenceService.this;
}
}
}
GeoFenceTransitionIntentService:
package com.example.admin.reminderapp.geofencing;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
import android.util.Log;
import com.example.admin.reminderapp.R;
import com.example.admin.reminderapp.activity.HomeActivity;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;
import java.util.ArrayList;
import java.util.List;
/**
* An {#link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
* <p/>
* TODO: Customize class - update intent actions, extra parameters and static
* helper methods.
*/
public class GeofenceTransitionsIntentService extends IntentService {
protected static final String TAG = "GeofenceTransitionsIS";
Boolean isMessageTone, isMessageVibrate;
String messageTone;
public GeofenceTransitionsIntentService() {
super(TAG);
Log.i(TAG, "GeofenceTransitionsIntentService");
}
#Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate");
}
// #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 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);
// Log.i(TAG, geofenceTransitionDetails);
// } else {
// // Log the error.
// Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
// geofenceTransition));
// }
// }
#Override
protected void onHandleIntent(Intent intent) {
Log.i(TAG, "Intent");
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.getErrorCode());
Log.e(TAG, errorMessage);
return;
}
int geofenceTransition = geofencingEvent.getGeofenceTransition();
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
String geofenceTransitionDetails = getGeofenceTransitionDetails(
this,
geofenceTransition,
triggeringGeofences
);
sendNotification(geofenceTransitionDetails);
Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition));
}
}
private String getGeofenceTransitionDetails(
Context context,
int geofenceTransition,
List<Geofence> triggeringGeofences) {
String geofenceTransitionString = getTransitionString(geofenceTransition);
ArrayList triggeringGeofencesIdsList = new ArrayList();
for (Geofence geofence : triggeringGeofences) {
triggeringGeofencesIdsList.add(geofence.getRequestId());
}
String triggeringGeofencesIdsString = TextUtils.join(", ", triggeringGeofencesIdsList);
return geofenceTransitionString + ": " + triggeringGeofencesIdsString;
}
private void sendNotification(String notificationDetails) {
int notifyColor = getResources().getColor(R.color.primary);
SharedPreferences prefs = getSharedPreferences("NotificationPrefs", MODE_PRIVATE);
isMessageTone = prefs.getBoolean("is_message_notification", false);
isMessageVibrate = prefs.getBoolean("is_message_vibrate", false);
messageTone = prefs.getString("message_notificationTone", "");
SharedPreferences preferences = getSharedPreferences("Theme", MODE_PRIVATE);
int storedPreference = preferences.getInt("storedInt", 0);
if (storedPreference == 0 || storedPreference == 1) {
notifyColor = getResources().getColor(R.color.primary);
} else if (storedPreference == 2) {
notifyColor = getResources().getColor(R.color.primary_theme2);
} else if (storedPreference == 3) {
notifyColor = getResources().getColor(R.color.primary_theme3);
} else if (storedPreference == 4) {
notifyColor = getResources().getColor(R.color.primary_theme4);
} else if (storedPreference == 5) {
notifyColor = getResources().getColor(R.color.primary_theme5);
}
Intent notificationIntent = new Intent(getApplicationContext(), HomeActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(HomeActivity.class);
stackBuilder.addNextIntent(notificationIntent);
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Define the notification settings.
builder.setSmallIcon(R.drawable.ic_directions_black)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_directions_black))
.setColor(notifyColor)
.setContentTitle(notificationDetails)
.setContentText(getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
if (isMessageTone) {
if (messageTone.equals("")) {
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
builder.setSound(defaultSoundUri);
} else {
builder.setSound(Uri.parse(messageTone));
}
if (isMessageVibrate) {
builder.setVibrate(new long[]{0, 1000});
}
}
builder.setAutoCancel(true);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(0, builder.build());
}
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);
}
}
}
Problem:
I am able to get the toast "Geofence added". But when I am selecting my current location for fencing, its not triggering any notification. Before when I used to do this from activity, it use to trigger the notification if my device location is in fencing which I have added.
Please help me to find the issue.
Thanks.
I have done this way:
I have created GeoFenceObserversationService singleton class.
GeoFenceObserversationService.java:
public class GeoFenceObserversationService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<Status> {
protected static final String TAG = "GeoFenceObserversationService";
protected GoogleApiClient mGoogleApiClient;
protected ArrayList<Geofence> mGeofenceList;
private boolean mGeofencesAdded;
private SharedPreferences mSharedPreferences;
private static GeoFenceObserversationService mInstant;
public static GeoFenceObserversationService getInstant(){
return mInstant;
}
#Override
public void onCreate() {
super.onCreate();
mInstant = this;
mGeofenceList = new ArrayList<Geofence>();
mSharedPreferences = getSharedPreferences(AppConstants.SHARED_PREFERENCES_NAME, MODE_PRIVATE);
mGeofencesAdded = mSharedPreferences.getBoolean(AppConstants.GEOFENCES_ADDED_KEY, false);
buildGoogleApiClient();
}
#Override
public void onDestroy() {
mGoogleApiClient.disconnect();
super.onDestroy();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
protected void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
#Override
public void onConnected(Bundle connectionHint) {
}
#Override
public void onConnectionFailed(ConnectionResult result) {
}
#Override
public void onConnectionSuspended(int cause) {
}
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeofenceList);
return builder.build();
}
public void addGeofences() {
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
}
populateGeofenceList();
if(!mGeofenceList.isEmpty()){
try {
LocationServices.GeofencingApi.addGeofences(mGoogleApiClient, getGeofencingRequest(), getGeofencePendingIntent()).setResultCallback(this);
} catch (SecurityException securityException) {
securityException.printStackTrace();
}
}
}
public void removeGeofences() {
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
}
try {
LocationServices.GeofencingApi.removeGeofences(mGoogleApiClient,getGeofencePendingIntent()).setResultCallback(this);
} catch (SecurityException securityException) {
securityException.printStackTrace();
}
}
public void onResult(Status status) {
if (status.isSuccess()) {
mGeofencesAdded = !mGeofencesAdded;
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(AppConstants.GEOFENCES_ADDED_KEY, mGeofencesAdded);
editor.apply();
} else {
String errorMessage = AppConstants.getErrorString(this,status.getStatusCode());
Log.i("Geofence", errorMessage);
}
}
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private void populateGeofenceList() {
mGeofenceList.clear();
List<GeoFencingResponce> geoFenceList = getGeofencesList;
if(geoFenceList!=null&&!geoFenceList.isEmpty()){
for (GeoFencingResponce obj : geoFenceList){
mGeofenceList.add(obj.getGeofence());
Log.i(TAG,"Registered Geofences : " + obj.Id+"-"+obj.Name+"-"+obj.Lattitude+"-"+obj.Longitude);
}
}
}
}
AppConstant:
public static final String SHARED_PREFERENCES_NAME = PACKAGE_NAME + ".SHARED_PREFERENCES_NAME";
public static final String GEOFENCES_ADDED_KEY = PACKAGE_NAME + ".GEOFENCES_ADDED_KEY";
public static final String DETECTED_GEOFENCES = "detected_geofences";
public static final String DETECTED_BEACONS = "detected_beacons";
public static String getErrorString(Context context, int errorCode) {
Resources mResources = context.getResources();
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
return mResources.getString(R.string.geofence_not_available);
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return mResources.getString(R.string.geofence_too_many_geofences);
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return mResources.getString(R.string.geofence_too_many_pending_intents);
default:
return mResources.getString(R.string.unknown_geofence_error);
}
}
Where I started Service ? From Application class
startService(new Intent(getApplicationContext(),GeoFenceObserversationService.class));
How I registered Geofences ?
GeoFenceObserversationService.getInstant().addGeofences();
Hope this will help you.