From 3e543444d5b229938e1e503ebc3ae23b4921da72 Mon Sep 17 00:00:00 2001 From: y20k Date: Tue, 6 Sep 2016 17:27:04 +0200 Subject: [PATCH] Trackbook got a notification. Double tracking while showing the map removed. While recording the track is now red. Bug fixes. --- .../java/org/y20k/trackbook/MainActivity.java | 61 ++++-- .../y20k/trackbook/MainActivityFragment.java | 121 ++++++++---- .../org/y20k/trackbook/TrackerService.java | 59 ++++-- .../java/org/y20k/trackbook/core/Track.java | 39 +++- .../trackbook/helpers/LocationHelper.java | 36 ++-- .../org/y20k/trackbook/helpers/MapHelper.java | 67 +++++-- .../trackbook/helpers/NotificationHelper.java | 174 +++++++++++++++++- .../y20k/trackbook/helpers/TrackbookKeys.java | 18 +- .../ic_my_location_crumb_red_24dp.xml | 10 + .../ic_my_location_crumb_transparent_24dp.xml | 5 + .../drawable/ic_my_location_white_24dp.xml | 2 +- ...c_notification_large_not_tracking_48dp.xml | 12 ++ .../ic_notification_large_tracking_48dp.xml | 12 ++ .../drawable/ic_notification_small_24dp.xml | 12 ++ .../main/res/drawable/ic_stop_white_36dp.xml | 10 + app/src/main/res/menu/menu_main.xml | 6 +- app/src/main/res/values/strings.xml | 10 +- 17 files changed, 538 insertions(+), 116 deletions(-) create mode 100644 app/src/main/res/drawable/ic_my_location_crumb_red_24dp.xml create mode 100644 app/src/main/res/drawable/ic_my_location_crumb_transparent_24dp.xml create mode 100644 app/src/main/res/drawable/ic_notification_large_not_tracking_48dp.xml create mode 100644 app/src/main/res/drawable/ic_notification_large_tracking_48dp.xml create mode 100644 app/src/main/res/drawable/ic_notification_small_24dp.xml create mode 100644 app/src/main/res/drawable/ic_stop_white_36dp.xml diff --git a/app/src/main/java/org/y20k/trackbook/MainActivity.java b/app/src/main/java/org/y20k/trackbook/MainActivity.java index 9f41b12..c28881b 100644 --- a/app/src/main/java/org/y20k/trackbook/MainActivity.java +++ b/app/src/main/java/org/y20k/trackbook/MainActivity.java @@ -18,13 +18,18 @@ package org.y20k.trackbook; import android.Manifest; import android.annotation.TargetApi; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.location.Location; import android.os.Build; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.content.ContextCompat; +import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; @@ -55,8 +60,10 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { private boolean mTracking; private boolean mPermissionsGranted; private List mMissingPermissions; + private View mRootView; private FloatingActionButton mFloatingActionButton; private MainActivityFragment mMainActivityFragment; + private BroadcastReceiver mTrackingStoppedReceiver; @Override @@ -66,7 +73,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { // set state of tracking mTracking = false; if (savedInstanceState != null) { - mTracking = savedInstanceState.getBoolean(INSTANCE_TRACKING_STARTED, false); + mTracking = savedInstanceState.getBoolean(INSTANCE_TRACKING_STATE, false); } // check permissions on Android 6 and higher @@ -85,6 +92,11 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { // set up main layout setupLayout(); + // register broadcast receiver for stopped tracking + mTrackingStoppedReceiver = createTrackingStoppedReceiver(); + IntentFilter trackingStoppedIntentFilter = new IntentFilter(ACTION_TRACKING_STOPPED); + LocalBroadcastManager.getInstance(this).registerReceiver(mTrackingStoppedReceiver, trackingStoppedIntentFilter); + } @@ -102,9 +114,9 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { // handle action bar options menu selection switch (item.getItemId()) { - // CASE SETTINGS - case R.id.action_settings: - LogHelper.v(LOG_TAG, "Settings was selected"); + // CASE ABOUT + case R.id.action_bar_about: + LogHelper.v(LOG_TAG, "About was selected"); // TODO remove return true; // CASE DEFAULT @@ -116,7 +128,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { @Override protected void onSaveInstanceState(Bundle outState) { - outState.putBoolean(INSTANCE_TRACKING_STARTED, mTracking); + outState.putBoolean(INSTANCE_TRACKING_STATE, mTracking); super.onSaveInstanceState(outState); } @@ -132,6 +144,15 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { } + @Override + public void onDestroy() { + super.onDestroy(); + + // disable broadcast receiver + LocalBroadcastManager.getInstance(this).unregisterReceiver(mTrackingStoppedReceiver); + } + + @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { @@ -213,31 +234,33 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { /* Handles tap on the record button */ private void handleFloatingActionButtonClick(View view) { if (mTracking) { + // show snackbar Snackbar.make(view, R.string.snackbar_message_tracking_stopped, Snackbar.LENGTH_SHORT).setAction("Action", null).show(); // change state - mFloatingActionButton.setImageResource(R.drawable.ic_fiber_manual_record_white_24dp); - mTracking = false; + // --> is handled by broadcast receiver // stop tracker service Intent intent = new Intent(this, TrackerService.class); intent.setAction(ACTION_STOP); startService(intent); - LogHelper.v(LOG_TAG, "Stopping tracker service."); - } else { + // show snackbar Snackbar.make(view, R.string.snackbar_message_tracking_started, Snackbar.LENGTH_SHORT).setAction("Action", null).show(); // change state - mFloatingActionButton.setImageResource(R.drawable.ic_fiber_manual_record_red_24dp); mTracking = true; + setFloatingActionButtonState(); + + // get last location from MainActivity Fragment + Location lastLocation = mMainActivityFragment.getCurrentBestLocation(); // start tracker service Intent intent = new Intent(this, TrackerService.class); intent.setAction(ACTION_START); + intent.putExtra(EXTRA_LAST_LOCATION, lastLocation); startService(intent); - LogHelper.v(LOG_TAG, "Starting tracker service."); } // update tracking state in MainActivityFragment @@ -274,4 +297,20 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { return permissions; } + + /* Creates receiver for stopped tracking */ + private BroadcastReceiver createTrackingStoppedReceiver() { + return new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // change state + mTracking = false; + setFloatingActionButtonState(); + + // pass tracking state to MainActivityFragment + mMainActivityFragment.setTrackingState(false); + } + }; + } + } diff --git a/app/src/main/java/org/y20k/trackbook/MainActivityFragment.java b/app/src/main/java/org/y20k/trackbook/MainActivityFragment.java index 86e890c..91a3498 100644 --- a/app/src/main/java/org/y20k/trackbook/MainActivityFragment.java +++ b/app/src/main/java/org/y20k/trackbook/MainActivityFragment.java @@ -74,7 +74,8 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { private ItemizedIconOverlay mMyLocationOverlay; private ItemizedIconOverlay mTrackOverlay; private Location mCurrentBestLocation; - private boolean mTacking; + private boolean mTrackerServiceRunning; + private boolean mLocalTrackerRunning; /* Constructor (default) */ @@ -99,16 +100,11 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { } // restore tracking state - mTacking = false; + mTrackerServiceRunning = false; if (savedInstanceState != null) { - mTacking = savedInstanceState.getBoolean(INSTANCE_TRACKING_STARTED, false); + mTrackerServiceRunning = savedInstanceState.getBoolean(INSTANCE_TRACKING_STATE, false); } - // register broadcast receiver for new WayPoints - mTrackUpdatedReceiver = createTrackUpdatedReceiver(); - IntentFilter trackUpdatedIntentFilter = new IntentFilter(ACTION_TRACK_UPDATED); - LocalBroadcastManager.getInstance(mActivity).registerReceiver(mTrackUpdatedReceiver, trackUpdatedIntentFilter); - // acquire reference to Location Manager mLocationManager = (LocationManager) mActivity.getSystemService(Context.LOCATION_SERVICE); @@ -134,6 +130,11 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { mCurrentBestLocation = LocationHelper.determineLastKnownLocation(mLocationManager); } + // register broadcast receiver for new WayPoints + mTrackUpdatedReceiver = createTrackUpdatedReceiver(); + IntentFilter trackUpdatedIntentFilter = new IntentFilter(ACTION_TRACK_UPDATED); + LocalBroadcastManager.getInstance(mActivity).registerReceiver(mTrackUpdatedReceiver, trackUpdatedIntentFilter); + } @@ -165,7 +166,7 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { mCurrentBestLocation = savedInstanceState.getParcelable(INSTANCE_CURRENT_LOCATION); } else if (mCurrentBestLocation != null) { // fallback or first run: set map to current position - GeoPoint position = new GeoPoint(mCurrentBestLocation.getLatitude(), mCurrentBestLocation.getLongitude()); + GeoPoint position = convertToGeoPoint(mCurrentBestLocation); mController.setCenter(position); mController.setZoom(16); } @@ -184,8 +185,8 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { mMapView.getOverlays().add(compassOverlay); // mark user's location on map - if (mCurrentBestLocation != null) { - mMyLocationOverlay = MapHelper.createMyLocationOverlay(mActivity, mCurrentBestLocation, LocationHelper.isNewLocation(mCurrentBestLocation), mTacking); + if (mCurrentBestLocation != null && !mTrackerServiceRunning) { + mMyLocationOverlay = MapHelper.createMyLocationOverlay(mActivity, mCurrentBestLocation, LocationHelper.isNewLocation(mCurrentBestLocation)); mMapView.getOverlays().add(mMyLocationOverlay); } @@ -197,8 +198,21 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { public void onResume() { super.onResume(); - // start tracking position - startFindingLocation(); + // start preliminary tracking - if no TrackerService is running + if (!mTrackerServiceRunning) { + startPreliminaryTracking(); + } + + // center map on current position - if TrackerService is running + if (mTrackerServiceRunning) { + mController.setCenter(convertToGeoPoint(mCurrentBestLocation)); + } + + // draw track on map - if available + if (mTrack != null) { + drawTrackOverlay(mTrack); + } + } @@ -206,8 +220,8 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { public void onPause() { super.onPause(); - // disable location listeners - LocationHelper.removeLocationListeners(mLocationManager, mGPSListener, mNetworkListener); + // disable preliminary location listeners + stopPreliminaryTracking(); } @@ -215,9 +229,6 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { public void onDestroyView(){ super.onDestroyView(); - // unregister broadcast receiver - LocalBroadcastManager.getInstance(mActivity).unregisterReceiver(mTrackUpdatedReceiver); - // deactivate map mMapView.onDetach(); } @@ -228,6 +239,9 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { // reset first start state mFirstStart = true; + // disable broadcast receivers + LocalBroadcastManager.getInstance(mActivity).unregisterReceiver(mTrackUpdatedReceiver); + super.onDestroy(); } @@ -239,7 +253,7 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { switch (item.getItemId()) { // CASE MY LOCATION - case R.id.action_my_location: + case R.id.action_bar_my_location: Toast.makeText(mActivity, mActivity.getString(R.string.toast_message_my_location), Toast.LENGTH_LONG).show(); @@ -253,12 +267,11 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { GeoPoint position; if (mCurrentBestLocation != null) { // app has a current best estimate location - position = new GeoPoint(mCurrentBestLocation.getLatitude(), mCurrentBestLocation.getLongitude()); } else { // app does not have any location fix mCurrentBestLocation = LocationHelper.determineLastKnownLocation(mLocationManager); - position = new GeoPoint(mCurrentBestLocation.getLatitude(), mCurrentBestLocation.getLongitude()); } + position = convertToGeoPoint(mCurrentBestLocation); // center map on current position mController.setCenter(position); @@ -284,26 +297,45 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { outState.putInt(INSTANCE_ZOOM_LEVEL, mMapView.getZoomLevel()); outState.putParcelable(INSTANCE_CURRENT_LOCATION, mCurrentBestLocation); outState.putParcelable(INSTANCE_TRACK, mTrack); - outState.putBoolean(INSTANCE_TRACKING_STARTED, mTacking); + outState.putBoolean(INSTANCE_TRACKING_STATE, mTrackerServiceRunning); super.onSaveInstanceState(outState); } - /* Sets tracking state */ - public void setTrackingState (boolean trackingStarted) { - mTacking = trackingStarted; + /* Setter for tracking state */ + public void setTrackingState (boolean trackingState) { + mTrackerServiceRunning = trackingState; + + // turn on/off tracking for MainActivity Fragment - prevent double tracking + if (mTrackerServiceRunning) { + stopPreliminaryTracking(); + } else if (!mLocalTrackerRunning){ + startPreliminaryTracking(); + if (mTrack != null) { + drawTrackOverlay(mTrack); + } + } + + // update marker updateMyLocationMarker(); + LogHelper.v(LOG_TAG, "TrackingState: " + trackingState); } - /* Start finding location for map */ - private void startFindingLocation() { + /* Getter for current best location */ + public Location getCurrentBestLocation() { + return mCurrentBestLocation; + } + + + /* Start preliminary tracking for map */ + private void startPreliminaryTracking() { + mLocalTrackerRunning = true; // create location listeners List locationProviders = mLocationManager.getProviders(true); if (locationProviders.contains(LocationManager.GPS_PROVIDER)) { mGPSListener = createLocationListener(); } - if (locationProviders.contains(LocationManager.NETWORK_PROVIDER)) { mNetworkListener = createLocationListener(); } @@ -313,6 +345,14 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { } + /* Removes gps and network location listeners */ + private void stopPreliminaryTracking() { + mLocalTrackerRunning = false; + // remove listeners + LocationHelper.removeLocationListeners(mLocationManager, mGPSListener, mNetworkListener); + } + + /* Creates listener for changes in location status */ private LocationListener createLocationListener() { return new LocationListener() { @@ -321,7 +361,6 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { if (LocationHelper.isBetterLocation(location, mCurrentBestLocation)) { // save location mCurrentBestLocation = location; - LogHelper.v(LOG_TAG, "Location isBetterLocation(!): " + location.getProvider()); // TODO remove // mark user's new location on map and remove last marker updateMyLocationMarker(); } @@ -345,15 +384,18 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { /* Updates marker for current user location */ private void updateMyLocationMarker() { mMapView.getOverlays().remove(mMyLocationOverlay); - mMyLocationOverlay = MapHelper.createMyLocationOverlay(mActivity, mCurrentBestLocation, LocationHelper.isNewLocation(mCurrentBestLocation),mTacking); - mMapView.getOverlays().add(mMyLocationOverlay); + // only update while not tracking + if (!mTrackerServiceRunning) { + mMyLocationOverlay = MapHelper.createMyLocationOverlay(mActivity, mCurrentBestLocation, LocationHelper.isNewLocation(mCurrentBestLocation)); + mMapView.getOverlays().add(mMyLocationOverlay); + } } /* Draws track onto overlay */ private void drawTrackOverlay(Track track) { mMapView.getOverlays().remove(mTrackOverlay); - mTrackOverlay = MapHelper.createTrackOverlay(mActivity, track); + mTrackOverlay = MapHelper.createTrackOverlay(mActivity, track, mTrackerServiceRunning); mMapView.getOverlays().add(mTrackOverlay); } @@ -370,16 +412,29 @@ public class MainActivityFragment extends Fragment implements TrackbookKeys { return new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent.hasExtra(EXTRA_TRACK)) { + if (intent.hasExtra(EXTRA_TRACK) && intent.hasExtra(EXTRA_LAST_LOCATION)) { + // draw track on map mTrack = intent.getParcelableExtra(EXTRA_TRACK); drawTrackOverlay(mTrack); Toast.makeText(mActivity, "New WayPoint.", Toast.LENGTH_LONG).show(); // TODO Remove + // center map over last location + mCurrentBestLocation = intent.getParcelableExtra(EXTRA_LAST_LOCATION); + mController.setCenter(convertToGeoPoint(mCurrentBestLocation)); + // clear intent + intent.removeExtra(EXTRA_TRACK); + intent.removeExtra(EXTRA_LAST_LOCATION); } } }; } + /* Converts Location to GeoPoint */ + private GeoPoint convertToGeoPoint (Location location) { + return new GeoPoint(location.getLatitude(), location.getLongitude()); + } + + /* Saves state of map */ private void saveMaoState(Context context) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); diff --git a/app/src/main/java/org/y20k/trackbook/TrackerService.java b/app/src/main/java/org/y20k/trackbook/TrackerService.java index d4494d3..355c4eb 100644 --- a/app/src/main/java/org/y20k/trackbook/TrackerService.java +++ b/app/src/main/java/org/y20k/trackbook/TrackerService.java @@ -32,6 +32,7 @@ import org.y20k.trackbook.core.Track; import org.y20k.trackbook.core.WayPoint; import org.y20k.trackbook.helpers.LocationHelper; import org.y20k.trackbook.helpers.LogHelper; +import org.y20k.trackbook.helpers.NotificationHelper; import org.y20k.trackbook.helpers.TrackbookKeys; import java.util.List; @@ -73,27 +74,40 @@ public class TrackerService extends Service implements TrackbookKeys { // create a new track mTrack = new Track(); - // add first location to track - mCurrentBestLocation = LocationHelper.determineLastKnownLocation(mLocationManager); + // get last location + if (intent.hasExtra(EXTRA_LAST_LOCATION)) { + mCurrentBestLocation = intent.getParcelableExtra(EXTRA_LAST_LOCATION); + } + // get last location - fallback + if (mCurrentBestLocation == null) { + mCurrentBestLocation = LocationHelper.determineLastKnownLocation(mLocationManager); + } + + // add last location as waypoint to track addWayPointToTrack(); // set timer to retrieve new locations and to prevent endless tracking - mTimer = new CountDownTimer(CONSTANT_MAXIMAL_DURATION, CONSTANT_TRACKING_INTERVAL) { + mTimer = new CountDownTimer(EIGHT_HOURS_IN_MILLISECONDS, FIFTEEN_SECONDS_IN_MILLISECONDS) { @Override - public void onTick(long l) { + public void onTick(long millisUntilFinished) { + // update track duration + mTrack.setTrackDuration(EIGHT_HOURS_IN_MILLISECONDS - millisUntilFinished); + // try to add WayPoint to Track addWayPointToTrack(); + // update notification + NotificationHelper.update(mTrack, true); } @Override public void onFinish() { - // TODO + // remove listeners + stopFindingLocation(); } }; mTimer.start(); // create gps and network location listeners startFindingLocation(); - } // ACTION STOP @@ -104,7 +118,7 @@ public class TrackerService extends Service implements TrackbookKeys { mTimer.cancel(); // remove listeners - LocationHelper.removeLocationListeners(mLocationManager, mGPSListener, mNetworkListener); + stopFindingLocation(); } // START_STICKY is used for services that are explicitly started and stopped as needed @@ -124,7 +138,7 @@ public class TrackerService extends Service implements TrackbookKeys { LogHelper.v(LOG_TAG, "onDestroy called."); // remove listeners - LocationHelper.removeLocationListeners(mLocationManager, mGPSListener, mNetworkListener); + stopFindingLocation(); // cancel notification stopForeground(true); @@ -162,6 +176,7 @@ public class TrackerService extends Service implements TrackbookKeys { Intent i = new Intent(); i.setAction(ACTION_TRACK_UPDATED); i.putExtra(EXTRA_TRACK, mTrack); + i.putExtra(EXTRA_LAST_LOCATION, mCurrentBestLocation); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i); } @@ -185,11 +200,11 @@ public class TrackerService extends Service implements TrackbookKeys { } public void onProviderEnabled(String provider) { - // TODO do something + LogHelper.v(LOG_TAG, "Location provider enabled: " + provider); } public void onProviderDisabled(String provider) { - // TODO do something + LogHelper.v(LOG_TAG, "Location provider disabled: " + provider); } }; } @@ -197,7 +212,9 @@ public class TrackerService extends Service implements TrackbookKeys { /* Creates gps and network location listeners */ private void startFindingLocation() { - LogHelper.v(LOG_TAG, "Setting up location listeners."); + LogHelper.v(LOG_TAG, "startFindingLocation"); // TODO remove + // put up notification + NotificationHelper.show(this,mTrack); // register location listeners and request updates List locationProviders = mLocationManager.getProviders(true); @@ -210,4 +227,24 @@ public class TrackerService extends Service implements TrackbookKeys { } + /* Removes gps and network location listeners */ + private void stopFindingLocation() { + LogHelper.v(LOG_TAG, "stopFindingLocation"); // TODO remove + // remove listeners + LocationHelper.removeLocationListeners(mLocationManager, mGPSListener, mNetworkListener); + + // notify MainActivityFragment + Intent i = new Intent(); + i.setAction(ACTION_TRACKING_STOPPED); + i.putExtra(EXTRA_TRACK, mTrack); + i.putExtra(EXTRA_LAST_LOCATION, mCurrentBestLocation); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i); + +// // cancel notification +// NotificationHelper.stop(); + // change notification + NotificationHelper.update(mTrack, false); + } + + } diff --git a/app/src/main/java/org/y20k/trackbook/core/Track.java b/app/src/main/java/org/y20k/trackbook/core/Track.java index a99920a..919e918 100644 --- a/app/src/main/java/org/y20k/trackbook/core/Track.java +++ b/app/src/main/java/org/y20k/trackbook/core/Track.java @@ -16,7 +16,6 @@ package org.y20k.trackbook.core; -import android.content.Context; import android.location.Location; import android.os.Parcel; import android.os.Parcelable; @@ -27,6 +26,7 @@ import org.y20k.trackbook.helpers.TrackbookKeys; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; /** @@ -39,9 +39,9 @@ public class Track implements TrackbookKeys, Parcelable { /* Main class variables */ - private Context mContext; private List mWayPoints; private float mTrackLength; + private long mTrackDuration; /* Constructor */ @@ -72,12 +72,6 @@ public class Track implements TrackbookKeys, Parcelable { }; - /* Set mContext needed by */ - public void setContext(Context context) { - mContext = context; - } - - /* Adds new waypoint */ public WayPoint addWayPoint(Location location) { // add up distance @@ -105,6 +99,12 @@ public class Track implements TrackbookKeys, Parcelable { } + /* Setter for duration of track */ + public void setTrackDuration(long trackDuration) { + mTrackDuration = trackDuration; + } + + /* Getter for mWayPoints */ public List getWayPoints() { return mWayPoints; @@ -117,12 +117,25 @@ public class Track implements TrackbookKeys, Parcelable { } + /* Getter for duration of track */ + public String getTrackDuration() { + return convertToReadableTime(mTrackDuration); + } + + + /* Getter for distance of track */ + public String getTrackDistance() { + float trackDistance = mWayPoints.get(mWayPoints.size()-1).getDistanceToStartingPoint(); + + return String.format ("%.0f", trackDistance) + "m"; + } + + /* Getter for location of specific WayPoint */ public Location getWayPointLocation(int index) { return mWayPoints.get(index).getLocation(); } - /* Adds distance to given location to length of track */ private float addDistanceToTrack(Location location) { // get number of previously recorded waypoints @@ -139,6 +152,14 @@ public class Track implements TrackbookKeys, Parcelable { } + /* Converts milliseconds to hh:mm:ss */ + private String convertToReadableTime(long milliseconds) { + return String.format("%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(milliseconds), + TimeUnit.MILLISECONDS.toMinutes(milliseconds) % TimeUnit.HOURS.toMinutes(1), + TimeUnit.MILLISECONDS.toSeconds(milliseconds) % TimeUnit.MINUTES.toSeconds(1)); + } + + @Override public int describeContents() { return 0; diff --git a/app/src/main/java/org/y20k/trackbook/helpers/LocationHelper.java b/app/src/main/java/org/y20k/trackbook/helpers/LocationHelper.java index d8046ca..dbe56b5 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/LocationHelper.java +++ b/app/src/main/java/org/y20k/trackbook/helpers/LocationHelper.java @@ -27,19 +27,12 @@ import java.util.List; /** * LocationHelper class */ -public final class LocationHelper { +public final class LocationHelper implements TrackbookKeys { /* Define log tag */ private static final String LOG_TAG = LocationHelper.class.getSimpleName(); - /* Main class variables */ -// private static final int TWO_MINUTES = 1000 * 1000 * 60 * 2; - private static final long FIVE_MINUTES = 5L * 60000000000L; // 2 minutes - private static final long TWO_MINUTES = 2L * 60000000000L; // 2 minutes - private static final long TWENTY_SECONDS = 20000000000L; // 20 seconds - - /* Determines last known location */ public static Location determineLastKnownLocation(LocationManager locationManager) { // define variables @@ -72,13 +65,14 @@ public final class LocationHelper { } } - // return best estimate location - if (isBetterLocation(networkLocation, gpsLocation)) { - LogHelper.v(LOG_TAG, "Best last known location came from: " + networkLocation.getProvider()); // TODO remove + if (gpsLocation == null) { return networkLocation; - } else { - LogHelper.v(LOG_TAG, "Best last known location came from: " + gpsLocation.getProvider()); // TODO remove + } else if (networkLocation == null) { return gpsLocation; + } else if (isBetterLocation(gpsLocation, networkLocation)) { + return gpsLocation; + } else { + return networkLocation; } } @@ -94,8 +88,8 @@ public final class LocationHelper { // check whether the new location fix is newer or older long timeDelta = location.getElapsedRealtimeNanos() - currentBestLocation.getElapsedRealtimeNanos(); - boolean isSignificantlyNewer = timeDelta > TWO_MINUTES; - boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES; + boolean isSignificantlyNewer = timeDelta > TWO_MINUTES_IN_NANOSECONDS; + boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES_IN_NANOSECONDS; boolean isNewer = timeDelta > 0; // if it's been more than two minutes since the current location, use the new location because the user has likely moved @@ -118,16 +112,12 @@ public final class LocationHelper { // determine location quality using a combination of timeliness and accuracy if (isMoreAccurate) { - LogHelper.v(LOG_TAG, "Location isMoreAccurate: " + location.getProvider()); // TODO remove return true; } else if (isNewer && !isLessAccurate) { - LogHelper.v(LOG_TAG, "Location isNewer && !isLessAccurate: " + location.getProvider()); // TODO remove return true; } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { - LogHelper.v(LOG_TAG, "Location isNewer && !isSignificantlyLessAccurate && isFromSameProvider: " + location.getProvider()); // TODO remove return true; } - LogHelper.v(LOG_TAG, "Location is not better: " + location.getProvider()); // TODO remove return false; } @@ -138,7 +128,7 @@ public final class LocationHelper { return false; } else { long locationTime = SystemClock.elapsedRealtimeNanos() - location.getElapsedRealtimeNanos(); - return locationTime < TWO_MINUTES; + return locationTime < TWO_MINUTES_IN_NANOSECONDS; } } @@ -148,15 +138,15 @@ public final class LocationHelper { float distance = newLocation.distanceTo(lastLocation); long timeDifference = newLocation.getElapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos(); - // distance is bigger than 10 meters and time difference bigger than 20 seconds - return distance > 10 && timeDifference >= TWENTY_SECONDS; + // distance is bigger than 10 meters and time difference bigger than 12 seconds + return distance > 10 && timeDifference >= TWELVE_SECONDS_IN_NANOSECONDS; } /* Checks if given location is a stop over */ public static boolean isStopOver(Location lastLocation, Location newLocation) { long timeDifference = newLocation.getElapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos(); - return timeDifference >= FIVE_MINUTES; + return timeDifference >= FIVE_MINUTES_IN_NANOSECONDS; } diff --git a/app/src/main/java/org/y20k/trackbook/helpers/MapHelper.java b/app/src/main/java/org/y20k/trackbook/helpers/MapHelper.java index 9876418..c35b7e6 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/MapHelper.java +++ b/app/src/main/java/org/y20k/trackbook/helpers/MapHelper.java @@ -21,6 +21,7 @@ import android.graphics.drawable.Drawable; import android.location.Location; import android.support.v7.widget.AppCompatDrawableManager; import android.text.format.DateFormat; +import android.widget.Toast; import org.osmdroid.util.GeoPoint; import org.osmdroid.views.overlay.ItemizedIconOverlay; @@ -42,17 +43,14 @@ public final class MapHelper { private static final String LOG_TAG = MapHelper.class.getSimpleName(); - /* Creates icon overlay for current position */ - public static ItemizedIconOverlay createMyLocationOverlay(Context context, Location currentBestLocation, boolean locationIsNew, boolean trackingStarted) { + /* Creates icon overlay for current position (used in MainActivity Fragment) */ + public static ItemizedIconOverlay createMyLocationOverlay(Context context, Location currentBestLocation, boolean locationIsNew) { final ArrayList overlayItems = new ArrayList<>(); - LogHelper.v(LOG_TAG, "Location is new? " + locationIsNew + " Provider: " + currentBestLocation.getProvider()); // TODO remove // create marker Drawable newMarker; - if (trackingStarted) { - newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_dot_red_24dp); - } else if (locationIsNew) { + if (locationIsNew) { newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_dot_blue_24dp); } else { newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_dot_grey_24dp); @@ -83,20 +81,62 @@ public final class MapHelper { /* Creates icon overlay for track */ - public static ItemizedIconOverlay createTrackOverlay(Context context, Track track){ + public static ItemizedIconOverlay createTrackOverlay(final Context context, Track track, boolean trackingActive){ + WayPoint wayPoint; + boolean currentPosition; + final int trackSize = track.getSize(); final List wayPoints = track.getWayPoints(); final ArrayList overlayItems = new ArrayList<>(); - for (WayPoint wayPoint : wayPoints) { + for (int i = 0 ; i < track.getSize() ; i++) { + // get waypoint and check if it is current position + wayPoint = wayPoints.get(i); + currentPosition = i == trackSize - 1; + // create marker Drawable newMarker; - if (wayPoint.getIsStopOver()) { - newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_crumb_grey_24dp); - } else { + + // CASE 1: Tracking active and waypoint is not current position + if (trackingActive && !currentPosition) { + if (wayPoint.getIsStopOver()) { + // stop over marker + newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_crumb_grey_24dp); + } else { + // default marker for this case + newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_crumb_red_24dp); + } + } + + // CASE 2: Tracking active and waypoint is current position + else if (trackingActive && currentPosition) { + if (wayPoint.getIsStopOver()) { + // stop over marker + newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_dot_grey_24dp); + } else { + // default marker for this case + newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_dot_red_24dp); + } + } + + // CASE 3: Tracking not active and waypoint is not current position + else if (!trackingActive && !currentPosition) { + if (wayPoint.getIsStopOver()) { + // stop over marker + newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_crumb_grey_24dp); + } else { + // default marker for this case + newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_crumb_blue_24dp); + } + } + + // CASE 4: Tracking not active and waypoint is current position + else { + // default marker newMarker = AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_my_location_crumb_blue_24dp); } - final String title = Float.toString(wayPoint.getDistanceToStartingPoint()); + + final String title = Float.toString(wayPoint.getDistanceToStartingPoint()) + " (" + wayPoint.getLocation().getProvider() + ")"; final String description = DateFormat.getDateFormat(context).format(wayPoint.getLocation().getTime()); final GeoPoint position = new GeoPoint(wayPoint.getLocation().getLatitude(), wayPoint.getLocation().getLongitude()); OverlayItem overlayItem = new OverlayItem(title, description, position); @@ -111,6 +151,7 @@ public final class MapHelper { new ItemizedIconOverlay.OnItemGestureListener() { @Override public boolean onItemSingleTapUp(final int index, final OverlayItem item) { + Toast.makeText(context, "Measured distance: " + item.getTitle(), Toast.LENGTH_LONG).show(); // TODO make string LogHelper.v(LOG_TAG, "Tap on a track crumb icon detected. Measured distance: " + item.getTitle()); return true; } @@ -125,6 +166,4 @@ public final class MapHelper { } - - } \ No newline at end of file diff --git a/app/src/main/java/org/y20k/trackbook/helpers/NotificationHelper.java b/app/src/main/java/org/y20k/trackbook/helpers/NotificationHelper.java index 2190154..28b167c 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/NotificationHelper.java +++ b/app/src/main/java/org/y20k/trackbook/helpers/NotificationHelper.java @@ -1,7 +1,177 @@ +/** + * NotificationHelper.java + * Implements the NotificationHelper class + * A NotificationHelper creates and configures a notification + * + * This file is part of + * TRACKBOOK - Movement Recorder for Android + * + * Copyright (c) 2016 - Y20K.org + * Licensed under the MIT-License + * http://opensource.org/licenses/MIT + * + * Trackbook uses osmdroid - OpenStreetMap-Tools for Android + * https://github.com/osmdroid/osmdroid + */ + package org.y20k.trackbook.helpers; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.support.graphics.drawable.VectorDrawableCompat; +import android.support.v4.app.TaskStackBuilder; +import android.support.v7.app.NotificationCompat; + +import org.y20k.trackbook.MainActivity; +import org.y20k.trackbook.R; +import org.y20k.trackbook.TrackerService; +import org.y20k.trackbook.core.Track; + + /** - * Created by solaris on 29/07/16. + * NotificationHelper class */ -public class NotificationHelper { +public class NotificationHelper implements TrackbookKeys { + + /* Define log tag */ + private static final String LOG_TAG = NotificationHelper.class.getSimpleName(); + + + /* Main class variables */ + private static Notification mNotification; + private static Service mService; + + /* Create and put up notification */ + public static void show(final Service service, Track track) { + // save service + mService = service; + + // build notification + mNotification = getNotificationBuilder(track, true).build(); + + // display notification + mService.startForeground(TRACKER_SERVICE_NOTIFICATION_ID, mNotification); + } + + + /* Updates the notification */ + public static void update(Track track, boolean tracking) { + + // build notification + mNotification = getNotificationBuilder(track, tracking).build(); + + // display updated notification + NotificationManager notificationManager = (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(TRACKER_SERVICE_NOTIFICATION_ID, mNotification); + + if (!tracking) { + // make notification swipe-able + mService.stopForeground(false); + } + + + } + + + /* Stop displaying notification */ + public static void stop() { + if (mService != null) { + mService.stopForeground(true); + } + } + + + /* Creates a notification builder */ + private static NotificationCompat.Builder getNotificationBuilder(Track track, boolean tracking) { + + String contentText = mService.getString(R.string.notification_content_distance) + ": " + track.getTrackDistance() + " | " + + mService.getString(R.string.notification_content_duration) + " : " + track.getTrackDuration(); + + // explicit intent for notification tap + Intent tapActionIntent = new Intent(mService, MainActivity.class); + + // explicit intent for stopping playback + Intent stopActionIntent = new Intent(mService, TrackerService.class); + stopActionIntent.setAction(ACTION_STOP); + + // artificial back stack for started Activity. + // -> navigating backward from the Activity leads to Home screen. + TaskStackBuilder stackBuilder = TaskStackBuilder.create(mService); +// // backstack: adds back stack for Intent (but not the Intent itself) +// stackBuilder.addParentStack(MainActivity.class); + // backstack: add explicit intent for notification tap + stackBuilder.addNextIntent(tapActionIntent); + + // pending intent wrapper for notification tap + PendingIntent tapActionPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + // pending intent wrapper for notification stop action + PendingIntent stopActionPendingIntent = PendingIntent.getService(mService, 0, stopActionIntent, 0); + + // construct notification in builder + NotificationCompat.Builder builder; + builder = new NotificationCompat.Builder(mService); + builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); + builder.setShowWhen(false); + builder.setContentIntent(tapActionPendingIntent); + builder.setSmallIcon(R.drawable.ic_notification_small_24dp); + builder.setLargeIcon(getNotificationIconLarge(tracking)); + if (tracking) { + builder.addAction(R.drawable.ic_stop_white_36dp, mService.getString(R.string.notification_stop), stopActionPendingIntent); + builder.setContentTitle(mService.getString(R.string.notification_title_trackbook_running)); + builder.setContentText(contentText); + // third line of text - only appears in expanded view + // builder.setSubText(); + } else { + builder.setContentTitle(mService.getString(R.string.notification_title_trackbook_not_running)); + builder.setContentText(contentText); + // third line of text - only appears in expanded view + // builder.setSubText(); + } + + return builder; + } + + + /* Get station image for notification's large icon */ + private static Bitmap getNotificationIconLarge(boolean tracking) { + + // get dimensions + Resources resources = mService.getResources(); + int height = (int) resources.getDimension(android.R.dimen.notification_large_icon_height); + int width = (int) resources.getDimension(android.R.dimen.notification_large_icon_width); + + Bitmap bitmap; + if (tracking) { + bitmap = getBitmap(R.drawable.ic_notification_large_tracking_48dp); + } else { + bitmap = getBitmap(R.drawable.ic_notification_large_not_tracking_48dp); + } + + return Bitmap.createScaledBitmap(bitmap, width, height, false); + } + + + /* Return a bitmap for a given resource id of a vector drawable */ + private static Bitmap getBitmap(int resource) { + VectorDrawableCompat drawable = VectorDrawableCompat.create(mService.getResources(), resource, null); + if (drawable != null) { + Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } else { + return null; + } + } + + + } diff --git a/app/src/main/java/org/y20k/trackbook/helpers/TrackbookKeys.java b/app/src/main/java/org/y20k/trackbook/helpers/TrackbookKeys.java index c989b3c..cbbb2ff 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/TrackbookKeys.java +++ b/app/src/main/java/org/y20k/trackbook/helpers/TrackbookKeys.java @@ -26,9 +26,11 @@ public interface TrackbookKeys { public static final String ACTION_START = "org.y20k.transistor.action.START"; public static final String ACTION_STOP = "org.y20k.transistor.action.STOP"; public static final String ACTION_TRACK_UPDATED = "TRACK_UPDATED"; + public static final String ACTION_TRACKING_STOPPED = "TRACKING_STOPPED"; /* EXTRAS */ public static final String EXTRA_TRACK = "TRACK"; + public static final String EXTRA_LAST_LOCATION = "LAST_LOCATION"; /* ARGS */ public static final String ARG_PERMISSIONS_GRANTED = "ArgPermissionsGranted"; @@ -48,21 +50,21 @@ public interface TrackbookKeys { public static final String INSTANCE_LONGITUDE = "longitude"; public static final String INSTANCE_ZOOM_LEVEL = "zoomLevel"; public static final String INSTANCE_CURRENT_LOCATION = "currentLocation"; - public static final String INSTANCE_TRACKING_STARTED = "trackingStarted"; + public static final String INSTANCE_TRACKING_STATE = "trackingState"; public static final String INSTANCE_TRACK = "track"; /* RESULTS */ /* CONSTANTS */ - public static final int CONSTANT_MINIMAL_STOP_TIME = 300000; // equals 5 minutes - public static final long CONSTANT_MAXIMAL_DURATION = 43200000; // equals 8 hours - public static final long CONSTANT_TRACKING_INTERVAL = 15000; // equals 15 seconds + public static final long EIGHT_HOURS_IN_MILLISECONDS = 43200000; // maximum tracking duration + public static final long FIFTEEN_SECONDS_IN_MILLISECONDS = 15000; // timer interval for tracking + public static final long FIVE_MINUTES_IN_NANOSECONDS = 5L * 60000000000L; // determines a stop over + public static final long TWO_MINUTES_IN_NANOSECONDS = 2L * 60000000000L; // defines an old location + public static final long TWELVE_SECONDS_IN_NANOSECONDS = 12000000000L; // defines a new location + /* MISC */ public static final int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124; - public static final int LOCATION_STATUS_OFFLINE = 0; - public static final int LOCATION_STATUS_OK = 1; - public static final int LOCATION_STATUS_GPS_ONLY = 2; - public static final int LOCATION_STATUS_NETWORK_ONLY = 3; + public static final int TRACKER_SERVICE_NOTIFICATION_ID = 1; } diff --git a/app/src/main/res/drawable/ic_my_location_crumb_red_24dp.xml b/app/src/main/res/drawable/ic_my_location_crumb_red_24dp.xml new file mode 100644 index 0000000..29baa58 --- /dev/null +++ b/app/src/main/res/drawable/ic_my_location_crumb_red_24dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_my_location_crumb_transparent_24dp.xml b/app/src/main/res/drawable/ic_my_location_crumb_transparent_24dp.xml new file mode 100644 index 0000000..4b8335e --- /dev/null +++ b/app/src/main/res/drawable/ic_my_location_crumb_transparent_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_my_location_white_24dp.xml b/app/src/main/res/drawable/ic_my_location_white_24dp.xml index 17a41bc..4bc02ca 100644 --- a/app/src/main/res/drawable/ic_my_location_white_24dp.xml +++ b/app/src/main/res/drawable/ic_my_location_white_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_notification_large_not_tracking_48dp.xml b/app/src/main/res/drawable/ic_notification_large_not_tracking_48dp.xml new file mode 100644 index 0000000..7d270c1 --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_large_not_tracking_48dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_notification_large_tracking_48dp.xml b/app/src/main/res/drawable/ic_notification_large_tracking_48dp.xml new file mode 100644 index 0000000..787e5cf --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_large_tracking_48dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_notification_small_24dp.xml b/app/src/main/res/drawable/ic_notification_small_24dp.xml new file mode 100644 index 0000000..e481483 --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_small_24dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_stop_white_36dp.xml b/app/src/main/res/drawable/ic_stop_white_36dp.xml new file mode 100644 index 0000000..25d2db4 --- /dev/null +++ b/app/src/main/res/drawable/ic_stop_white_36dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 50cd9c3..8007b2a 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -4,16 +4,16 @@ tools:context="org.y20k.trackbook.MainActivity"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 50fee6e..d829cc1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,8 +3,16 @@ Trackbook - Settings My Location + About + + + + Trackbook running + Trackbook not running + Stop + Duration + Distance Tracking stopped