diff --git a/app/src/main/java/org/y20k/trackbook/MainActivity.java b/app/src/main/java/org/y20k/trackbook/MainActivity.java index fdad2e3..245c974 100644 --- a/app/src/main/java/org/y20k/trackbook/MainActivity.java +++ b/app/src/main/java/org/y20k/trackbook/MainActivity.java @@ -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. diff --git a/app/src/main/java/org/y20k/trackbook/MainActivityMapFragment.java b/app/src/main/java/org/y20k/trackbook/MainActivityMapFragment.java index 1b30ab5..9ea38fe 100644 --- a/app/src/main/java/org/y20k/trackbook/MainActivityMapFragment.java +++ b/app/src/main/java/org/y20k/trackbook/MainActivityMapFragment.java @@ -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); } diff --git a/app/src/main/java/org/y20k/trackbook/TrackerService.java b/app/src/main/java/org/y20k/trackbook/TrackerService.java index c9b000f..b4a9eec 100644 --- a/app/src/main/java/org/y20k/trackbook/TrackerService.java +++ b/app/src/main/java/org/y20k/trackbook/TrackerService.java @@ -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 */ diff --git a/build.gradle b/build.gradle index 55a93bf..999754c 100644 --- a/build.gradle +++ b/build.gradle @@ -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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e6f73ae..53e5bb2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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