From 67382ce90a80e409f51edfab0bff6d8774643334 Mon Sep 17 00:00:00 2001 From: Ethan Dalool Date: Thu, 30 Mar 2023 21:49:26 -0700 Subject: [PATCH] Sleep GPS near homepoints, wait for significant motion sensor. First attempts, will test for a few days. --- .../java/net/voussoir/trkpt/MapFragment.kt | 169 ++++++++++------- .../java/net/voussoir/trkpt/TrackFragment.kt | 1 - .../java/net/voussoir/trkpt/TrackerService.kt | 179 ++++++++++++------ .../main/java/net/voussoir/trkpt/functions.kt | 12 +- .../main/res/drawable/ic_satellite_24dp.xml | 13 ++ app/src/main/res/drawable/ic_sleep_24dp.xml | 14 ++ app/src/main/res/layout/fragment_map.xml | 23 +++ 7 files changed, 277 insertions(+), 134 deletions(-) create mode 100644 app/src/main/res/drawable/ic_satellite_24dp.xml create mode 100644 app/src/main/res/drawable/ic_sleep_24dp.xml diff --git a/app/src/main/java/net/voussoir/trkpt/MapFragment.kt b/app/src/main/java/net/voussoir/trkpt/MapFragment.kt index 394887f..9c350e6 100644 --- a/app/src/main/java/net/voussoir/trkpt/MapFragment.kt +++ b/app/src/main/java/net/voussoir/trkpt/MapFragment.kt @@ -30,9 +30,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager -import android.widget.Button -import android.widget.EditText -import android.widget.TextView +import android.widget.* import androidx.activity.result.contract.ActivityResultContracts.RequestPermission import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -62,13 +60,9 @@ class MapFragment : Fragment() private var bound: Boolean = false val handler: Handler = Handler(Looper.getMainLooper()) - private var trackingState: Int = Keys.STATE_TRACKING_STOPPED - private var gpsProviderActive: Boolean = false - private var networkProviderActive: Boolean = false - private lateinit var currentBestLocation: Location var continuous_auto_center: Boolean = true - private lateinit var trackerService: TrackerService + private var trackerService: TrackerService? = null private lateinit var database_changed_listener: DatabaseChangedListener var thismapfragment: MapFragment? = null @@ -79,6 +73,8 @@ class MapFragment : Fragment() lateinit var zoom_in_button: FloatingActionButton lateinit var zoom_out_button: FloatingActionButton lateinit var currentLocationButton: FloatingActionButton + lateinit var map_current_time: TextView + lateinit var power_level_indicator: ImageButton private var current_track_overlay: Polyline? = null private var current_position_overlays = ArrayList() private var homepoints_overlays = ArrayList() @@ -107,8 +103,6 @@ class MapFragment : Fragment() update_main_button() } } - currentBestLocation = getLastKnownLocation(requireContext()) - trackingState = PreferencesHelper.loadTrackingState() } /* Overrides onStop from Fragment */ @@ -121,6 +115,8 @@ class MapFragment : Fragment() currentLocationButton = rootView.findViewById(R.id.location_button) zoom_in_button = rootView.findViewById(R.id.zoom_in_button) zoom_out_button = rootView.findViewById(R.id.zoom_out_button) + map_current_time = rootView.findViewById(R.id.map_current_time) + power_level_indicator = rootView.findViewById(R.id.power_level_indicator) mainButton = rootView.findViewById(R.id.main_button) locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE) @@ -200,9 +196,7 @@ class MapFragment : Fragment() trackbook.database_changed_listeners.add(database_changed_listener) } - create_current_position_overlays(currentBestLocation, trackingState) - - centerMap(currentBestLocation) + centerMap(getLastKnownLocation(requireContext())) current_track_overlay = null @@ -213,9 +207,14 @@ class MapFragment : Fragment() update_main_button() mainButton.setOnClickListener { - if (trackingState == Keys.STATE_TRACKING_ACTIVE) + val tracker = trackerService + if (tracker == null) { - trackerService.stopTracking() + return@setOnClickListener + } + if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE) + { + tracker.stopTracking() } else { @@ -224,7 +223,12 @@ class MapFragment : Fragment() handler.postDelayed(location_update_redraw, 0) } currentLocationButton.setOnClickListener { - centerMap(currentBestLocation, animated=true) + val tracker = trackerService + if (tracker == null) + { + return@setOnClickListener + } + centerMap(tracker.currentBestLocation, animated=true) } zoom_in_button.setOnClickListener { mapView.controller.setZoom(mapView.zoomLevelDouble + 0.5) @@ -256,6 +260,7 @@ class MapFragment : Fragment() { Log.i("VOUSSOIR", "MapFragment.onResume") super.onResume() + redraw() requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) // if (bound) { // trackerService.addGpsLocationListener() @@ -268,26 +273,31 @@ class MapFragment : Fragment() { Log.i("VOUSSOIR", "MapFragment.onPause") super.onPause() - if (::trackerService.isInitialized) - { - trackerService.mapfragment = null - } - saveBestLocationState(currentBestLocation) - if (bound && trackingState != Keys.STATE_TRACKING_ACTIVE) { - trackerService.removeGpsLocationListener() - trackerService.removeNetworkLocationListener() - trackerService.trackbook.database.commit() - } requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + + val tracker = trackerService + if (tracker == null) + { + return + } + saveBestLocationState(tracker.currentBestLocation) + tracker.mapfragment = null + if (bound && tracker.trackingState != Keys.STATE_TRACKING_ACTIVE) + { + tracker.removeGpsLocationListener() + tracker.removeNetworkLocationListener() + tracker.trackbook.database.commit() + } } /* Overrides onStop from Fragment */ override fun onStop() { super.onStop() - if (::trackerService.isInitialized) + val tracker = trackerService + if (tracker != null) { - trackerService.mapfragment = null + tracker.mapfragment = null } // unbind from TrackerService if (bound) @@ -311,7 +321,10 @@ class MapFragment : Fragment() { Log.i("VOUSSOIR", "MapFragment.onDestroy") super.onDestroy() - trackerService.mapfragment = null + if (trackerService != null) + { + trackerService!!.mapfragment = null + } requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } @@ -328,6 +341,8 @@ class MapFragment : Fragment() // permission denied - unbind service activity?.unbindService(connection) } + val gpsProviderActive = if (trackerService == null) false else trackerService!!.gpsProviderActive + val networkProviderActive = if (trackerService == null) false else trackerService!!.networkProviderActive toggleLocationErrorBar(gpsProviderActive, networkProviderActive) } @@ -344,7 +359,10 @@ class MapFragment : Fragment() { activity?.startService(intent) } - trackerService.startTracking() + if (trackerService != null) + { + trackerService!!.startTracking() + } } @@ -354,24 +372,14 @@ class MapFragment : Fragment() bound = false // unregister listener for changes in shared preferences PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener) - if (::trackerService.isInitialized) + if (trackerService != null) { - trackerService.mapfragment = null + trackerService!!.mapfragment = null } } private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key -> - when (key) - { - Keys.PREF_TRACKING_STATE -> - { - if (activity != null) - { - trackingState = PreferencesHelper.loadTrackingState() - } - } - } - update_main_button() + redraw() } fun centerMap(location: Location, animated: Boolean = false) { @@ -557,6 +565,7 @@ class MapFragment : Fragment() fun update_main_button() { + val tracker = trackerService mainButton.isEnabled = trackbook.database.ready currentLocationButton.isVisible = true if (! trackbook.database.ready) @@ -564,13 +573,13 @@ class MapFragment : Fragment() mainButton.text = requireContext().getString(R.string.button_not_ready) mainButton.icon = null } - else if (trackingState == Keys.STATE_TRACKING_STOPPED) + else if (tracker == null || tracker.trackingState == Keys.STATE_TRACKING_STOPPED) { mainButton.setIconResource(R.drawable.ic_fiber_manual_record_inactive_24dp) mainButton.text = requireContext().getString(R.string.button_start) mainButton.contentDescription = requireContext().getString(R.string.descr_button_start) } - else if (trackingState == Keys.STATE_TRACKING_ACTIVE) + else if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE) { mainButton.setIconResource(R.drawable.ic_fiber_manual_stop_24dp) mainButton.text = requireContext().getString(R.string.button_pause) @@ -609,11 +618,9 @@ class MapFragment : Fragment() } bound = true trackerService = serviceref - trackerService.mapfragment = thismapfragment + trackerService!!.mapfragment = thismapfragment // get state of tracking and update button if necessary - trackingState = trackerService.trackingState - update_main_button() - handler.postDelayed(location_update_redraw, 0) + redraw() // register listener for changes in shared preferences PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) // start listening for location updates @@ -625,27 +632,57 @@ class MapFragment : Fragment() } } + fun redraw() + { + // Log.i("VOUSSOIR", "MapFragment.redraw") + update_main_button() + val tracker = trackerService + if (tracker == null) + { + return + } + create_current_position_overlays(tracker.currentBestLocation, tracker.trackingState) + if (current_track_overlay == null) + { + create_track_overlay() + } + current_track_overlay!!.setPoints(tracker.recent_trackpoints_for_mapview) + + if (continuous_auto_center) + { + centerMap(tracker.currentBestLocation, animated=false) + } + + map_current_time.text = iso8601_local_noms(tracker.currentBestLocation.time) + + if (tracker.arrived_at_home == 0L) + { + power_level_indicator.setImageResource(R.drawable.ic_satellite_24dp) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + power_level_indicator.tooltipText = "GPS tracking at full power" + } + } + else if (tracker.location_interval == tracker.LOCATION_INTERVAL_SLEEP) + { + power_level_indicator.setImageResource(R.drawable.ic_sleep_24dp) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + power_level_indicator.tooltipText = "GPS sleeping until movement" + } + } + else + { + power_level_indicator.setImageResource(R.drawable.ic_homepoint_24dp) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + power_level_indicator.tooltipText = "You are at home" + } + } + } + val location_update_redraw: Runnable = object : Runnable { override fun run() { - Log.i("VOUSSOIR", "MapFragment.location_update_redraw") - currentBestLocation = trackerService.currentBestLocation - gpsProviderActive = trackerService.gpsProviderActive - networkProviderActive = trackerService.networkProviderActive - trackingState = trackerService.trackingState - - create_current_position_overlays(currentBestLocation, trackingState) - if (current_track_overlay == null) - { - create_track_overlay() - } - current_track_overlay!!.setPoints(trackerService.recent_trackpoints_for_mapview) - - if (continuous_auto_center) - { - centerMap(currentBestLocation, animated=false) - } + redraw() } } } diff --git a/app/src/main/java/net/voussoir/trkpt/TrackFragment.kt b/app/src/main/java/net/voussoir/trkpt/TrackFragment.kt index 10a272c..d8ef5f6 100644 --- a/app/src/main/java/net/voussoir/trkpt/TrackFragment.kt +++ b/app/src/main/java/net/voussoir/trkpt/TrackFragment.kt @@ -57,7 +57,6 @@ import org.osmdroid.views.overlay.TilesOverlay import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlayOptions import org.osmdroid.views.overlay.simplefastpoint.SimplePointTheme -import java.text.SimpleDateFormat import java.util.* class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener diff --git a/app/src/main/java/net/voussoir/trkpt/TrackerService.kt b/app/src/main/java/net/voussoir/trkpt/TrackerService.kt index de5ca21..3153b26 100644 --- a/app/src/main/java/net/voussoir/trkpt/TrackerService.kt +++ b/app/src/main/java/net/voussoir/trkpt/TrackerService.kt @@ -31,6 +31,10 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager +import android.hardware.Sensor +import android.hardware.SensorManager +import android.hardware.TriggerEvent +import android.hardware.TriggerEventListener import android.location.Location import android.location.LocationListener import android.location.LocationManager @@ -39,23 +43,14 @@ import android.media.ToneGenerator import android.os.Binder import android.os.Build import android.os.IBinder +import android.os.Vibrator import android.util.Log import androidx.annotation.RequiresApi import androidx.appcompat.content.res.AppCompatResources import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap -import net.voussoir.trkpt.helpers.PreferencesHelper -import net.voussoir.trkpt.helpers.getDefaultLocation -import net.voussoir.trkpt.helpers.getLastKnownLocation -import net.voussoir.trkpt.helpers.isAccurateEnough -import net.voussoir.trkpt.helpers.isBetterLocation -import net.voussoir.trkpt.helpers.isDifferentEnough -import net.voussoir.trkpt.helpers.isGpsEnabled -import net.voussoir.trkpt.helpers.isNetworkEnabled -import net.voussoir.trkpt.helpers.isRecentEnough -import net.voussoir.trkpt.helpers.iso8601_local -import net.voussoir.trkpt.helpers.random_device_id +import net.voussoir.trkpt.helpers.* import org.osmdroid.util.GeoPoint import java.lang.ref.WeakReference import java.util.* @@ -70,7 +65,11 @@ class TrackerService: Service() var device_id: String = random_device_id() var currentBestLocation: Location = getDefaultLocation() var lastCommit: Long = 0 - var location_min_time_ms: Long = 0 + var last_significant_motion: Long = 0 + var arrived_at_home: Long = 0 + var location_interval: Long = 0 + val LOCATION_INTERVAL_FULLPOWER: Long = 0 + val LOCATION_INTERVAL_SLEEP: Long = Keys.ONE_MINUTE_IN_MILLISECONDS private val RECENT_TRKPT_COUNT = 3600 private val DISPLACEMENT_LOCATION_COUNT = 5 lateinit var recent_displacement_locations: Deque @@ -94,7 +93,10 @@ class TrackerService: Service() var mapfragment: MapFragment? = null - private fun addGpsLocationListener() + private lateinit var sensor_manager: SensorManager + private var significant_motion_sensor: Sensor? = null + + private fun addGpsLocationListener(interval: Long) { if (! use_gps_location) { @@ -124,7 +126,7 @@ class TrackerService: Service() locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, - location_min_time_ms, + interval, 0f, gpsLocationListener, ) @@ -132,7 +134,7 @@ class TrackerService: Service() Log.i("VOUSSOIR", "Added GPS location listener.") } - private fun addNetworkLocationListener() + private fun addNetworkLocationListener(interval: Long) { if (! use_network_location) { @@ -162,7 +164,7 @@ class TrackerService: Service() locationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, - 0, + interval, 0f, networkLocationListener, ) @@ -198,6 +200,27 @@ class TrackerService: Service() } } + fun reset_location_listeners(interval: Long) + { + location_interval = interval + if (gpsLocationListenerRegistered) + { + removeGpsLocationListener() + } + if (networkLocationListenerRegistered) + { + removeNetworkLocationListener() + } + if (use_gps_location) + { + addGpsLocationListener(interval) + } + if (use_network_location) + { + addNetworkLocationListener(interval) + } + } + private fun createLocationListener(): LocationListener { return object : LocationListener @@ -208,17 +231,11 @@ class TrackerService: Service() // beeper.startTone(ToneGenerator.TONE_PROP_ACK, 150) - if (location.time == currentBestLocation.time) + if (location.time <= currentBestLocation.time) { return } - if (! isBetterLocation(location, currentBestLocation)) - { - Log.i("VOUSSOIR", "Not better than previous.") - return - } - currentBestLocation = location val mf = mapfragment @@ -241,16 +258,12 @@ class TrackerService: Service() Log.i("VOUSSOIR", "Omitting due to 0,0 location.") return } - if (! isRecentEnough(location)) - { - Log.i("VOUSSOIR", "Omitting due to not recent enough.") - return - } - if (! isAccurateEnough(location, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY)) - { - Log.i("VOUSSOIR", "Omitting due to not accurate enough.") - return - } + + // The Homepoint checks need to come before the other checks because if there + // is even the slightest chance that the user has left the homepoint, we want to + // wake back up to full power. We do not want to put this below the isAccurateEnough + // of isRecentEnough checks because we already know that the sleeping GPS produces + // very inaccurate points so if those bail early we'd stay in sleep mode. for ((index, homepoint) in trackbook.homepoints.withIndex()) { if (homepoint.location.distanceTo(location) < homepoint.radius) @@ -261,9 +274,42 @@ class TrackerService: Service() trackbook.homepoints.remove(homepoint) trackbook.homepoints.addFirst(homepoint) } + if (arrived_at_home == 0L) + { + Log.i("VOUSSOIR", "Arrived at home.") + arrived_at_home = System.currentTimeMillis() + } + else if (location_interval == LOCATION_INTERVAL_SLEEP || significant_motion_sensor == null) + { + // If we are already asleep, do not reset the listeners again because + // that immediately fetches a new location. + // If we cannot rely on the motion sensor, then don't sleep! + } + else if ((System.currentTimeMillis() - arrived_at_home) > Keys.ONE_MINUTE_IN_MILLISECONDS) + { + Log.i("VOUSSOIR", "Staying at home.") + reset_location_listeners(interval=LOCATION_INTERVAL_SLEEP) + } return } } + if (arrived_at_home > 0) + { + Log.i("VOUSSOIR", "Leaving home.") + arrived_at_home = 0 + reset_location_listeners(interval=LOCATION_INTERVAL_FULLPOWER) + } + + if (! isRecentEnough(location)) + { + Log.i("VOUSSOIR", "Omitting due to not recent enough.") + return + } + if (! isAccurateEnough(location, Keys.DEFAULT_THRESHOLD_LOCATION_ACCURACY)) + { + Log.i("VOUSSOIR", "Omitting due to not accurate enough.") + return + } if (recent_displacement_locations.isEmpty()) { // pass @@ -321,13 +367,20 @@ class TrackerService: Service() private fun displayNotification(): Notification { - val timestamp = iso8601_local(currentBestLocation.time) + val timestamp = iso8601_local_noms(currentBestLocation.time) if (shouldCreateNotificationChannel()) { createNotificationChannel() } - notification_builder.setContentText(timestamp) + if (location_interval == LOCATION_INTERVAL_SLEEP) + { + notification_builder.setContentText("${timestamp} (sleeping)") + } + else + { + notification_builder.setContentText(timestamp) + } notification_builder.setWhen(currentBestLocation.time) if (trackingState == Keys.STATE_TRACKING_ACTIVE) @@ -400,8 +453,7 @@ class TrackerService: Service() { Log.i("VOUSSOIR", "TrackerService.onBind") bound = true - addGpsLocationListener() - addNetworkLocationListener() + reset_location_listeners(interval=LOCATION_INTERVAL_FULLPOWER) return binder } @@ -410,8 +462,7 @@ class TrackerService: Service() { Log.i("VOUSSOIR", "TrackerService.onRebind") bound = true - addGpsLocationListener() - addNetworkLocationListener() + reset_location_listeners(interval=LOCATION_INTERVAL_FULLPOWER) } /* Overrides onUnbind from Service */ @@ -466,6 +517,31 @@ class TrackerService: Service() override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.i("VOUSSOIR", "TrackerService.onStartCommand") + + val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + sensor_manager = getSystemService(Context.SENSOR_SERVICE) as SensorManager + significant_motion_sensor = sensor_manager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION) + if (significant_motion_sensor != null) + { + val triggerEventListener = object : TriggerEventListener() { + override fun onTrigger(event: TriggerEvent?) { + Log.i("VOUSSOIR", "Significant motion") + // beeper.startTone(ToneGenerator.TONE_PROP_ACK, 150) + vibrator.vibrate(50) + last_significant_motion = System.currentTimeMillis() + arrived_at_home = 0L + if (location_interval == LOCATION_INTERVAL_SLEEP) + { + reset_location_listeners(LOCATION_INTERVAL_FULLPOWER) + val mf = mapfragment + mf?.handler?.postDelayed(mf.location_update_redraw, 0) + } + sensor_manager.requestTriggerSensor(this, significant_motion_sensor) + } + } + sensor_manager.requestTriggerSensor(triggerEventListener, significant_motion_sensor) + } + // SERVICE RESTART (via START_STICKY) if (intent == null) { @@ -507,8 +583,8 @@ class TrackerService: Service() fun startTracking() { Log.i("VOUSSOIR", "TrackerService.startTracking") - addGpsLocationListener() - addNetworkLocationListener() + arrived_at_home = 0 + reset_location_listeners(interval=LOCATION_INTERVAL_FULLPOWER) trackingState = Keys.STATE_TRACKING_ACTIVE PreferencesHelper.saveTrackingState(trackingState) recent_displacement_locations.clear() @@ -519,7 +595,8 @@ class TrackerService: Service() { Log.i("VOUSSOIR", "TrackerService.stopTracking") trackbook.database.commit() - + arrived_at_home = 0 + reset_location_listeners(interval=LOCATION_INTERVAL_FULLPOWER) trackingState = Keys.STATE_TRACKING_STOPPED PreferencesHelper.saveTrackingState(trackingState) recent_displacement_locations.clear() @@ -533,26 +610,12 @@ class TrackerService: Service() Keys.PREF_LOCATION_GPS -> { use_gps_location = PreferencesHelper.load_location_gps() - if (use_gps_location) - { - addGpsLocationListener() - } - else - { - removeGpsLocationListener() - } + reset_location_listeners(interval=LOCATION_INTERVAL_FULLPOWER) } Keys.PREF_LOCATION_NETWORK -> { use_network_location = PreferencesHelper.load_location_network() - if (use_network_location) - { - addNetworkLocationListener() - } - else - { - removeNetworkLocationListener() - } + reset_location_listeners(interval=LOCATION_INTERVAL_FULLPOWER) } Keys.PREF_USE_IMPERIAL_UNITS -> { diff --git a/app/src/main/java/net/voussoir/trkpt/functions.kt b/app/src/main/java/net/voussoir/trkpt/functions.kt index c43815d..8783b93 100644 --- a/app/src/main/java/net/voussoir/trkpt/functions.kt +++ b/app/src/main/java/net/voussoir/trkpt/functions.kt @@ -20,16 +20,10 @@ fun iso8601_local(timestamp: Long): String return iso8601_format.format(timestamp) } -fun iso8601(datetime: Date): String +fun iso8601_local_noms(timestamp: Long): String { - return iso8601(datetime.time) -} - -fun iso8601_parse(datetime: String): Date -{ - val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - iso8601_format.timeZone = TimeZone.getTimeZone("UTC") - return iso8601_format.parse(datetime) + val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss") + return iso8601_format.format(timestamp) } fun random_int(): Int diff --git a/app/src/main/res/drawable/ic_satellite_24dp.xml b/app/src/main/res/drawable/ic_satellite_24dp.xml new file mode 100644 index 0000000..9ebe2af --- /dev/null +++ b/app/src/main/res/drawable/ic_satellite_24dp.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_sleep_24dp.xml b/app/src/main/res/drawable/ic_sleep_24dp.xml new file mode 100644 index 0000000..50d95b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_sleep_24dp.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml index 8f16db0..4ed53c1 100644 --- a/app/src/main/res/layout/fragment_map.xml +++ b/app/src/main/res/layout/fragment_map.xml @@ -87,6 +87,29 @@ app:layout_constraintEnd_toEndOf="parent" app:tint="@color/location_button_icon" /> + + + +