tracker service is now a so-called bound service

This commit is contained in:
y20k 2018-04-06 18:14:34 +02:00
parent 39a4cbbe41
commit 8264a2b2af
5 changed files with 357 additions and 78 deletions

View file

@ -20,15 +20,18 @@ import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
@ -52,6 +55,7 @@ import android.widget.Button;
import android.widget.Toast;
import org.osmdroid.config.Configuration;
import org.y20k.trackbook.core.Track;
import org.y20k.trackbook.helpers.DialogHelper;
import org.y20k.trackbook.helpers.LogHelper;
import org.y20k.trackbook.helpers.NightModeHelper;
@ -75,6 +79,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
/* Main class variables */
private TrackerService mTrackerService;
private BottomNavigationView mBottomNavigationView;
private NonSwipeableViewPager mViewPager;
private SectionsPagerAdapter mSectionsPagerAdapter;
@ -94,6 +99,8 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
private int mFloatingActionButtonState;
private int mSelectedTab;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -140,6 +147,10 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
protected void onStart() {
super.onStart();
// bind to TrackerService
Intent intent = new Intent(this, TrackerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
// register broadcast receiver for changed tracking state
mTrackingChangedReceiver = createTrackingChangedReceiver();
IntentFilter trackingStoppedIntentFilter = new IntentFilter(ACTION_TRACKING_STATE_CHANGED);
@ -170,6 +181,14 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
}
@Override
protected void onStop() {
super.onStop();
unbindService(mConnection);
mBound = false;
}
@Override
public void onDestroy() {
super.onDestroy();
@ -250,13 +269,21 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
}
/* Request the current Track from TrackerService */
public void requestTrack() {
if (mBound) {
mTrackerService.sendTrackUpdate();
}
}
/* Handles the visual state after a save action */
private void handleStateAfterSave() {
// display and update tracks tab
mBottomNavigationView.setSelectedItemId(R.id.navigation_last_tracks);
// dismiss notification
startTrackerService(ACTION_DISMISS, null);
dismissNotification();
// hide Floating Action Button sub menu
showFloatingActionButtonMenu(false);
@ -267,17 +294,11 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
}
/* Start/stop tracker service */
private void startTrackerService(String intentAction, @Nullable Location lastLocation) {
// build intent
/* Start tracker service */
private void startTrackerService() {
// start service so that it keeps running after unbind
Intent intent = new Intent(this, TrackerService.class);
intent.setAction(intentAction);
if (lastLocation != null && intentAction.equals(ACTION_START)) {
intent.putExtra(EXTRA_LAST_LOCATION, lastLocation);
}
// communicate with service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && intentAction.equals(ACTION_START)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// ... start service in foreground to prevent it being killed on Oreo
startForegroundService(intent);
} else {
@ -286,6 +307,39 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
}
/* Start recording movements */
private void startRecording(Location lastLocation) {
startTrackerService();
if (mBound) {
mTrackerService.startTracking(lastLocation);
}
}
/* Resume recording movements */
private void resumeRecording() {
startTrackerService();
if (mBound) {
mTrackerService.resumeTracking();
}
}
/* Stop recording movements */
private void stopRecording() {
if (mBound) {
mTrackerService.stopTracking();
}
}
/* Dismiss notification */
private void dismissNotification() {
if (mBound) {
mTrackerService.dismissNotification();
}
}
/* Handles the visual state after a save action */
private void handleStateAfterClear() {
@ -293,7 +347,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
Toast.makeText(this, getString(R.string.toast_message_track_clear), Toast.LENGTH_LONG).show();
// dismiss notification
startTrackerService(ACTION_DISMISS, null);
dismissNotification();
// hide Floating Action Button sub menu
showFloatingActionButtonMenu(false);
@ -335,7 +389,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
Snackbar.make(view, R.string.snackbar_message_tracking_resumed, Snackbar.LENGTH_SHORT).setAction("Action", null).show();
// resume tracking
startTrackerService(ACTION_RESUME, null);
resumeRecording();
// hide sub menu
showFloatingActionButtonMenu(false);
@ -500,7 +554,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
Snackbar.make(view, R.string.snackbar_message_tracking_started, Snackbar.LENGTH_SHORT).setAction("Action", null).show();
// start tracker service
startTrackerService(ACTION_START, lastLocation);
startRecording(lastLocation);
} else {
Toast.makeText(this, getString(R.string.toast_message_location_services_not_ready), Toast.LENGTH_LONG).show();
@ -518,7 +572,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
// --> is handled by broadcast receiver
// stop tracker service
startTrackerService(ACTION_STOP, null);
stopRecording();
break;
@ -756,6 +810,25 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
// }
/**
* Defines callbacks for service binding, passed to bindService()
*/
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
TrackerService.LocalBinder binder = (TrackerService.LocalBinder) service;
mTrackerService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
/**
* Inner class: SectionsPagerAdapter that returns a fragment corresponding to one of the tabs.

View file

@ -228,9 +228,7 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
// CASE 1: recording active
if (mTrackerServiceRunning) {
// request an updated track recording from service
Intent intent = new Intent(mActivity, TrackerService.class);
intent.setAction(ACTION_TRACK_REQUEST);
mActivity.startService(intent);
((MainActivity)mActivity).requestTrack();
}
// CASE 2: recording stopped - temp file exists
@ -580,6 +578,8 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
/* Loads state tracker service from preferences */
private void loadTrackerServiceState(Context context) {
// TODO: get state directly from service, create a ServiceConnection.
// see: https://github.com/ena1106/FragmentBoundServiceExample/blob/master/app/src/main/java/it/ena1106/fragmentboundservice/BoundFragment.java
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
mTrackerServiceRunning = settings.getBoolean(PREFS_TRACKER_SERVICE_RUNNING, false);
}

View file

@ -31,6 +31,7 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
@ -50,6 +51,7 @@ import org.y20k.trackbook.helpers.StorageHelper;
import org.y20k.trackbook.helpers.TrackbookKeys;
import java.util.List;
import java.util.Random;
import static android.hardware.Sensor.TYPE_STEP_COUNTER;
@ -79,6 +81,8 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
private boolean mTrackerServiceRunning;
private boolean mLocationSystemSetting;
private final IBinder mBinder = new LocalBinder(); // todo move to onCreate
@Override
public void onCreate() {
@ -102,69 +106,101 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
}
@Override
public IBinder onBind(Intent intent) {
// a client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
// return mAllowRebind; // todo change
return true;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// check if user did turn off location in device settings
if (!mLocationSystemSetting) {
LogHelper.i(LOG_TAG, "Location Setting is turned off.");
Toast.makeText(getApplicationContext(), R.string.toast_message_location_offline, Toast.LENGTH_LONG).show();
stopTracking();
return START_STICKY;
}
// RESTART CHECK: checking for empty intent - try to get saved track
if (intent == null || intent.getAction() == null) {
LogHelper.w(LOG_TAG, "Null-Intent received. Trying to restart tracking.");
startTracking(intent, false);
}
// ACTION START
else if (intent.getAction().equals(ACTION_START) && mLocationSystemSetting) {
startTracking(intent, true);
}
// ACTION RESUME
else if (intent.getAction().equals(ACTION_RESUME) && mLocationSystemSetting) {
startTracking(intent, false);
}
// ACTION STOP
else if (intent.getAction().equals(ACTION_STOP) || !mLocationSystemSetting) {
mTrackerServiceRunning = false;
if (mTrack != null && mTimer != null) {
stopTracking();
} else {
// handle error - save state
saveTrackerServiceState(mTrackerServiceRunning, FAB_STATE_DEFAULT);
}
if (ACTION_STOP.equals(intent.getAction())) {
stopTracking();
}
// ACTION RESUME
else if (ACTION_RESUME.equals(intent.getAction())) {
resumeTracking();
}
// ACTION DISMISS
else if (intent.getAction().equals(ACTION_DISMISS)) {
// save state
saveTrackerServiceState(mTrackerServiceRunning, FAB_STATE_DEFAULT);
// dismiss notification
mNotificationManager.cancel(TRACKER_SERVICE_NOTIFICATION_ID); // todo check if necessary?
stopForeground(true);
}
// ACTION TRACK REQUEST
else if (intent.getAction().equals(ACTION_TRACK_REQUEST)) {
// send track via broadcast
sendTrackUpdate();
}
// // check if user did turn off location in device settings
// if (!mLocationSystemSetting) {
// LogHelper.i(LOG_TAG, "Location Setting is turned off.");
// Toast.makeText(getApplicationContext(), R.string.toast_message_location_offline, Toast.LENGTH_LONG).show();
// stopTracking();
// return START_STICKY;
// }
//
// // RESTART CHECK: checking for empty intent - try to get saved track
// if (intent == null || intent.getAction() == null) {
// LogHelper.w(LOG_TAG, "Null-Intent received. Trying to restart tracking.");
// startTracking(intent, false);
// }
//
// // ACTION START
// else if (intent.getAction().equals(ACTION_START) && mLocationSystemSetting) {
// startTracking(intent, true);
// }
//
// // ACTION RESUME
// else if (intent.getAction().equals(ACTION_RESUME) && mLocationSystemSetting) {
// startTracking(intent, false);
// }
//
// // ACTION STOP
// else if (intent.getAction().equals(ACTION_STOP) || !mLocationSystemSetting) {
// mTrackerServiceRunning = false;
// if (mTrack != null && mTimer != null) {
// stopTracking();
// } else {
// // handle error - save state
// saveTrackerServiceState(mTrackerServiceRunning, FAB_STATE_DEFAULT);
// }
// }
//
// // ACTION DISMISS
// else if (intent.getAction().equals(ACTION_DISMISS)) {
// // save state
// saveTrackerServiceState(mTrackerServiceRunning, FAB_STATE_DEFAULT);
// // dismiss notification
// mNotificationManager.cancel(TRACKER_SERVICE_NOTIFICATION_ID); // todo check if necessary?
// stopForeground(true);
// }
//
// // ACTION TRACK REQUEST
// else if (intent.getAction().equals(ACTION_TRACK_REQUEST)) {
// // send track via broadcast
// sendTrackUpdate();
// }
// START_STICKY is used for services that are explicitly started and stopped as needed
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
// @Nullable
// @Override
// public IBinder onBind(Intent intent) {
// return null;
// }
@Override
@ -204,6 +240,153 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
/* Start tracking location */
public void startTracking(Location lastLocation) {
if (mLocationSystemSetting) {
LogHelper.v(LOG_TAG, "Start tracking");
// create a new track - if requested
mTrack = new Track();
// get last location
if (lastLocation != null) {
mCurrentBestLocation = lastLocation;
} else {
mCurrentBestLocation = LocationHelper.determineLastKnownLocation(mLocationManager);
}
// initialize step counter
mStepCountOffset = 0;
// begin recording
recordMovements();
} else {
LogHelper.i(LOG_TAG, "Location Setting is turned off.");
Toast.makeText(getApplicationContext(), R.string.toast_message_location_offline, Toast.LENGTH_LONG).show();
}
}
/* Resume tracking after stop/pause */
public void resumeTracking() {
if (mLocationSystemSetting) {
LogHelper.v(LOG_TAG, "Recording resumed");
// create a new track - if requested
StorageHelper storageHelper = new StorageHelper(this);
if (storageHelper.tempFileExists()) {
// load temp track file
mTrack = storageHelper.loadTrack(FILE_TEMP_TRACK);
// try to mark last waypoint as stopover
int lastWayPoint = mTrack.getWayPoints().size() - 1;
if (lastWayPoint >= 0) {
mTrack.getWayPoints().get(lastWayPoint).setIsStopOver(true);
}
} else {
// fallback, if tempfile did not exist
LogHelper.e(LOG_TAG, "Unable to find previously saved track temp file.");
mTrack = new Track();
}
// get last location
if (mTrack.getSize() > 0) {
mCurrentBestLocation = mTrack.getWayPointLocation(mTrack.getSize() -1);
} else {
mCurrentBestLocation = LocationHelper.determineLastKnownLocation(mLocationManager);
}
// initialize step counter
mStepCountOffset = mTrack.getStepCount();
// begin recording
recordMovements();
} else {
LogHelper.i(LOG_TAG, "Location Setting is turned off.");
Toast.makeText(getApplicationContext(), R.string.toast_message_location_offline, Toast.LENGTH_LONG).show();
}
}
/* Record movements */
private void recordMovements() {
// add last location as WayPoint to track
addWayPointToTrack();
// put up notification
displayNotification(true);
// create gps and network location listeners
startFindingLocation();
// start timer that periodically request a location update
startIntervalTimer();
// start counting steps
startStepCounter();
// register content observer for changes in System Settings
this.getContentResolver().registerContentObserver(android.provider.Settings.Secure.CONTENT_URI, true, mSettingsContentObserver);
// start service in foreground
startForeground(TRACKER_SERVICE_NOTIFICATION_ID, mNotification);
}
private void startStepCounter() {
boolean stepCounterAvailable;
stepCounterAvailable = mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(TYPE_STEP_COUNTER), SensorManager.SENSOR_DELAY_UI);
if (stepCounterAvailable) {
LogHelper.v(LOG_TAG, "Pedometer sensor available: Registering listener.");
} else {
LogHelper.i(LOG_TAG, "Pedometer sensor not available.");
mTrack.setStepCount(-1);
}
}
/* Set timer to retrieve new locations and to prevent endless tracking */
private void startIntervalTimer() {
mTimer = new CountDownTimer(EIGHT_HOURS_IN_MILLISECONDS, FIFTEEN_SECONDS_IN_MILLISECONDS) {
@Override
public void onTick(long millisUntilFinished) {
// update track duration - and add duration from previously interrupted / paused session
long previouslyRecordedDuration = mTrack.getTrackDuration();
long duration = EIGHT_HOURS_IN_MILLISECONDS - millisUntilFinished + previouslyRecordedDuration;
mTrack.setDuration(duration);
// try to add WayPoint to Track
addWayPointToTrack();
// update notification
mNotification = NotificationHelper.getUpdatedNotification(TrackerService.this, mNotificationBuilder, mTrack);
mNotificationManager.notify(TRACKER_SERVICE_NOTIFICATION_ID, mNotification);
// save a temp file in case the service has been killed by the system
SaveTempTrackAsyncHelper saveTempTrackAsyncHelper = new SaveTempTrackAsyncHelper();
saveTempTrackAsyncHelper.execute();
}
@Override
public void onFinish() {
// stop tracking after eight hours
stopTracking();
}
};
mTimer.start();
}
/* Display notification */
private void displayNotification(boolean trackingState) {
mNotificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANEL_ID_RECORDING_CHANNEL);
mNotification = NotificationHelper.getNotification(this, mNotificationBuilder, mTrack, trackingState);
mNotificationManager.notify(TRACKER_SERVICE_NOTIFICATION_ID, mNotification); // todo check if necessary in pre Android O
}
/* Start tracking location */ // todo remove
private void startTracking(@Nullable Intent intent, boolean createNewTrack) {
LogHelper.v(LOG_TAG, "Service received command: START");
@ -301,8 +484,8 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
/* Stop tracking location */
private void stopTracking() {
LogHelper.v(LOG_TAG, "Service received command: STOP");
public void stopTracking() {
LogHelper.v(LOG_TAG, "Recording stopped");
// store current date and time
mTrack.setRecordingEnd();
@ -318,9 +501,7 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
saveTempTrackAsyncHelper.execute();
// change notification
mNotificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANEL_ID_RECORDING_CHANNEL);
mNotification = NotificationHelper.getNotification(this, mNotificationBuilder, mTrack, false);
mNotificationManager.notify(TRACKER_SERVICE_NOTIFICATION_ID, mNotification);
displayNotification(false);
// remove listeners
stopFindingLocation();
@ -334,6 +515,17 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
}
/* Dismiss notification */
public void dismissNotification() {
// save state
saveTrackerServiceState(mTrackerServiceRunning, FAB_STATE_DEFAULT);
// cancel notification
mNotificationManager.cancel(TRACKER_SERVICE_NOTIFICATION_ID); // todo check if necessary?
stopForeground(true);
}
/* Adds a new WayPoint to current track */
private void addWayPointToTrack() {
@ -376,7 +568,7 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
/* Broadcasts a track update */
private void sendTrackUpdate() {
public void sendTrackUpdate() {
if (mTrack != null) {
Intent i = new Intent();
i.setAction(ACTION_TRACK_UPDATED);
@ -467,6 +659,20 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
}
/**
* Inner class: Local Binder that returns this service
*/
public class LocalBinder extends Binder {
TrackerService getService() {
// return this instance of TrackerService so clients can call public methods
return TrackerService.this;
}
}
/**
* End of inner class
*/
/**
* Inner class: SettingsContentObserver is a custom ContentObserver for changes in Android Settings
*/

View file

@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.android.tools.build:gradle:3.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -1,6 +1,6 @@
#Thu Nov 02 15:43:54 CET 2017
#Wed Apr 04 15:52:45 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip