I am developing a step counter app where I count the number of steps walked and update it to the server at midnight. I have a service running continuously to do all this.
Here is my service:
public class StepCounterService extends Service implements SensorEventListener, StepListener, WebServiceInterface, GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, LocationListener {
private static final int SERVICE_ID = 27;
private static final int SEND_SESSION_REQUEST_CODE = 1;
private static final int SEND_ACTIVITY_REQUEST_CODE = 2;
private static final int MAIN_NOTIFICATION_ID = 3;
private static final int SECONDARY_NOTIFICATION_ID = 4;
private LocalBroadcastManager broadcaster;
static final public String STEP_INCREMENT = "com.app.STEP_INCREMENTED";
static final public String SESSION_COMPLETE = "com.app.SESSION_COMPLETE";
static final public String ACTIVITY_COMPLETE = "com.app.ACTIVITY_COMPLETE";
static final public String STEP_INCREMENT_KEY = "step_count";
Session session;
private StepDetector stepDetector;
private SensorManager sensorManager;
private Sensor sensor;
private int numberOfSteps = 0;
private GoogleApiClient googleApiClient;
private LocationRequest mLocationRequest;
double currentLatitude;
double currentLongitude;
ArrayList<String> arrayListLocations;
private AlarmManager sendActivityAlarmManager;
private PendingIntent activityAlarmIntent;
private NotificationManager notificationManager;
RemoteViews contentView;
PowerManager.WakeLock wl;
private String TAG = "Wake Lock Tag";
private int SET_LAST_LOCATION_REQUEST_CODE = 5;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
return START_STICKY;
}
@Override
public void onTaskRemoved(Intent rootIntent) {
// TODO Auto-generated method stub
System.out.println("---- In onTaskRemoved Function");
restartKilledService();
}
@Override
public void onDestroy() {
System.out.println("---- In onDestroy Function");
if (wl != null) {
wl.release();
}
super.onDestroy();
restartKilledService();
}
void restartKilledService() {
System.out.println("---- In restartKilledService Function");
Intent restartService = new Intent(getApplicationContext(), StepCounterService.class);
restartService.setPackage(getPackageName());
PendingIntent restartServicePI = PendingIntent.getService(getApplicationContext(), StepCounterService.SERVICE_ID, restartService, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
alarmService.set(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime() + 100, restartServicePI);
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(getApplicationContext().POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wl.acquire();
super.onCreate();
session = new Session(this);
buildGoogleApiClient();
broadcaster = LocalBroadcastManager.getInstance(this);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
stepDetector = new StepDetector();
stepDetector.registerListener(StepCounterService.this);
Context ctx = getApplicationContext();
Calendar cal = Calendar.getInstance();
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
long interval = 1000 * 60 * 5; // 5 minutes in milliseconds
Intent serviceIntent = new Intent(ctx, StepCounterService.class);
PendingIntent servicePendingIntent = PendingIntent.getService(ctx, StepCounterService.SERVICE_ID, serviceIntent, PendingIntent.FLAG_CANCEL_CURRENT);
am.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), interval, servicePendingIntent);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
notificationManager = (NotificationManager) getSystemService(Activity.NOTIFICATION_SERVICE);
contentView = new RemoteViews(getPackageName(), R.layout.notification_layout);
contentView.setImageViewResource(R.id.image, R.drawable.notif_icon);
if (session.getUser() != null && !(session.getUser().getName().equals("")))
updateMainNotification(session.getTodaySteps() + "");
startAlarm();
}
protected synchronized void buildGoogleApiClient() {
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
googleApiClient.connect();
mLocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(10 * 1000) // 10 seconds, in milliseconds
.setFastestInterval(1 * 1000);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
stepDetector.updateAccel(event.timestamp, event.values[0], event.values[1], event.values[2]);
}
}
@Override
public void step(long timeNs) {
numberOfSteps = session.getTodaySteps();
numberOfSteps++;
sendStepIncrementBroadcast(numberOfSteps);
session.setTodaySteps(numberOfSteps);
if (session.getUser() != null && !(session.getUser().getName().equals("")))
updateMainNotification(numberOfSteps + "");
else {
try {
notificationManager.cancel(MAIN_NOTIFICATION_ID);
} catch (Exception e) {
}
}
}
public void sendStepIncrementBroadcast(int numberOfSteps) {
Intent intent = new Intent(STEP_INCREMENT);
intent.putExtra(STEP_INCREMENT_KEY, numberOfSteps);
broadcaster.sendBroadcast(intent);
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
public float[] getDataFromSteps(int stepsCount) {
float caloriesCount = 0;
float creditsCount = 0;
try {
double adjustedWeight = Double.parseDouble(session.getUser().getWeight()) / LinksAndKeys.weightAdjuster;
caloriesCount = Math.round(((adjustedWeight * LinksAndKeys.metValue) / LinksAndKeys.setPace) * (stepsCount / LinksAndKeys.stepsPerMile));
caloriesCount = caloriesCount * 1.2f;
creditsCount = caloriesCount / 25.4f;
} catch (Exception e) {
caloriesCount = 0;
creditsCount = 0;
}
float[] resultantFloat = {caloriesCount, creditsCount};
return resultantFloat;
}
private void startAlarm() {
sendActivityAlarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, ActivityCompleteReceiver.class);
activityAlarmIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 58);
// calendar.set(Calendar.HOUR_OF_DAY, generateRandomTime()[0]);
// calendar.set(Calendar.MINUTE, generateRandomTime()[1]);
if (System.currentTimeMillis() > calendar.getTimeInMillis()) {
calendar.add(Calendar.DAY_OF_YEAR, 1);
}
sendActivityAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
AlarmManager.INTERVAL_DAY, activityAlarmIntent);
}
private void updateMainNotification(String stepsValue) {
String title = "Today: " + stepsValue + " steps - " + LinksAndKeys.decimalFormat.format(getDataFromSteps(session.getTodaySteps())[1]) + " FIMOs";
String message = "Keep Walking and Keep Earning";
contentView.setTextViewText(R.id.textViewTitle, title);
contentView.setTextViewText(R.id.textViewMessage, message);
Intent notificationIntent = new Intent(StepCounterService.this, SplashActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent intent = PendingIntent.getActivity(StepCounterService.this, 0, notificationIntent, 0);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.running_icon)
.setContent(contentView).setContentIntent(intent);
Notification notification = mBuilder.build();
notification.flags |= Notification.FLAG_ONGOING_EVENT;
// notificationManager.notify(MAIN_NOTIFICATION_ID, notification);
startForeground(MAIN_NOTIFICATION_ID, notification);
}
private void updateReminderNotification() {
String title = "A gentle reminder";
String message = "Its time to get on track";
contentView.setTextViewText(R.id.textViewTitle, title);
contentView.setTextViewText(R.id.textViewMessage, message);
Intent notificationIntent = new Intent(StepCounterService.this, SplashActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent intent = PendingIntent.getActivity(StepCounterService.this, 0, notificationIntent, 0);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.running_icon)
.setContent(contentView).setContentIntent(intent);
Notification notification = mBuilder.build();
notificationManager.notify(SECONDARY_NOTIFICATION_ID, notification);
}
private int[] generateRandomTime() {
int[] timeIntegers = new int[2];
final Random r = new Random();
timeIntegers[0] = r.nextInt(58 - 56) + 56;
timeIntegers[1] = r.nextInt(59 - 1) + 1;
return timeIntegers;
}
}
Here is the Activity Complete Receiver:
public class ActivityCompleteReceiver extends BroadcastReceiver implements WebServiceInterface {
Session session;
private LocalBroadcastManager broadcaster;
static final public String ACTIVITY_COMPLETE = "com.fimo.ACTIVITY_COMPLETE";
private static final int SEND_ACTIVITY_REQUEST_CODE = 1;
Gson gson;
MyDatabase myDatabase;
UserActivity currentUserActivity;
@Override
public void onReceive(Context context, Intent intent) {
session = new Session(context);
broadcaster = LocalBroadcastManager.getInstance(context);
myDatabase = new MyDatabase(context);
gson = new Gson();
sendActivityToServer(context, session.getUser().getId(), session.getTodaySteps());
}
private void sendActivityToServer(Context context, String id, int steps) {
Calendar c = Calendar.getInstance();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String date = dateFormat.format(c.getTime());
UserActivity userActivity = new UserActivity();
userActivity.setDate(date);
userActivity.setSteps(steps);
userActivity.setCalories(getDataFromSteps(steps)[0]);
userActivity.setCredits(getDataFromSteps(steps)[1]);
currentUserActivity = userActivity;
if (isNetworkAvailable(context)) {
HashMap<String, String> paramsList = new HashMap<>();
ArrayList<UserActivity> arrayListUserActivity = myDatabase.getAllUserActivities();
arrayListUserActivity.add(userActivity);
paramsList.put(LinksAndKeys.ID_KEY, id);
paramsList.put(LinksAndKeys.DATA_KEY, gson.toJson(arrayListUserActivity));
Log.d("Receiver Request ----", id + " - " + gson.toJson(arrayListUserActivity));
WebServiceController webServiceController = new WebServiceController(
context, ActivityCompleteReceiver.this);
String hitURL = LinksAndKeys.SEND_ACTIVITY_URL;
webServiceController.sendSilentRequest(false, hitURL, paramsList, SEND_ACTIVITY_REQUEST_CODE);
} else {
myDatabase.addUserActivity(currentUserActivity);
currentUserActivity = null;
Intent in = new Intent(ACTIVITY_COMPLETE);
broadcaster.sendBroadcast(in);
session.setTodaySteps(0);
}
}
private boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager
= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
@Override
public void getResponse(int responseCode, String responseString, String requestType, int requestCode) {
if (requestCode == SEND_ACTIVITY_REQUEST_CODE && responseCode == 200) {
try {
JSONObject responseObject = new JSONObject(responseString);
String message = responseObject.getString("message");
if (message.equals("Success")) {
myDatabase.deleteAllUserActivities();
JSONObject jsonObject = responseObject.getJSONObject("data");
session.setServerCredits(Float.parseFloat(jsonObject.getString("credits")));
Intent in = new Intent(ACTIVITY_COMPLETE);
broadcaster.sendBroadcast(in);
session.setTodaySteps(0);
} else {
myDatabase.addUserActivity(currentUserActivity);
currentUserActivity = null;
Intent in = new Intent(ACTIVITY_COMPLETE);
broadcaster.sendBroadcast(in);
session.setTodaySteps(0);
}
} catch (Exception e) {
myDatabase.addUserActivity(currentUserActivity);
currentUserActivity = null;
Intent in = new Intent(ACTIVITY_COMPLETE);
broadcaster.sendBroadcast(in);
session.setTodaySteps(0);
}
} else {
if (currentUserActivity != null) {
myDatabase.addUserActivity(currentUserActivity);
currentUserActivity = null;
Intent in = new Intent(ACTIVITY_COMPLETE);
broadcaster.sendBroadcast(in);
session.setTodaySteps(0);
}
}
}
public float[] getDataFromSteps(int stepsCount) {
float caloriesCount = 0;
float creditsCount = 0;
try {
double adjustedWeight = Double.parseDouble(session.getUser().getWeight()) / LinksAndKeys.weightAdjuster;
caloriesCount = Math.round(((adjustedWeight * LinksAndKeys.metValue) / LinksAndKeys.setPace) * (stepsCount / LinksAndKeys.stepsPerMile));
caloriesCount = caloriesCount * 1.2f;
creditsCount = caloriesCount / 25.4f;
} catch (Exception e) {
caloriesCount = 0;
creditsCount = 0;
}
float[] resultantFloat = {caloriesCount, creditsCount};
return resultantFloat;
}
}
The code written is supposed to behave on this way:
Everyday count the number of steps taken by the user. -> At midnight at a particular time between 11.56-11.59, send the data to activity complete receiver -> The receiver receives the data and tries to send it to the server if internet is available. If not, then it saves it to local database -> After saving or sending, reset the steps to 0 and service starts counting again from 0 for the next day.
The problem is the service is not actually working some days at the given time and receiver does not receive the activity complete intent. If I keep the phone on and test it by setting a time which is a few minutes from current time, service works. But if the phone is kept untouched from a long time then I think this case happens as the service stops working. This is my guess, the actual problem might be something else. Any suggestions or solutions are highly appreciated.
You can do this by making your own Interface where you declare for example " isServiceRunning() ". You can then bind your Activity to your Service, run the method isServiceRunning(), the Service will check for itself if it is running or not and returns a boolean to your Activity.
As of API level 19, using AlarmManager.setRepeating()
which you are doing to call your ActivityCompleteReceiver
, is not reliably accurate. Android can delay the delivery of this alarm if it wants to. If you really want this to be triggered at an exact time each day you should do the following:
Use AlarmManager.setExact()
and set one alarm (not a repeating alarm) for the next trigger time. When this alarm gets delivered, send your statistics or whatever you want and then call AlarmManager.setExact()
to set the next alarm (for the next day). You should avoid using setRepeating()
unless you absolutely need to use it.
You need to watch what you are doing with wake locks, as your current code keeps a partial wake lock all the time and this will prevent the device from going to sleep which will drain the battery. Read up on how to optimize battery use.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With