Use Android service for fetching location

This commit is contained in:
jannis 2019-08-19 14:53:22 +02:00
parent aa8a213af2
commit 0ea7535c50
8 changed files with 194 additions and 111 deletions

View File

@ -26,6 +26,7 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -49,6 +50,7 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name=".location.LocationListener"></service>
</application> </application>
</manifest> </manifest>

View File

@ -23,6 +23,9 @@ import android.content.Context;
import androidx.room.Room; import androidx.room.Room;
import java.util.ArrayList;
import java.util.List;
import de.tadris.fitness.data.AppDatabase; import de.tadris.fitness.data.AppDatabase;
import de.tadris.fitness.location.LocationListener; import de.tadris.fitness.location.LocationListener;
@ -40,14 +43,13 @@ public class Instance {
} }
public AppDatabase db; public AppDatabase db;
public LocationListener locationListener;
public UserPreferences userPreferences; public UserPreferences userPreferences;
public List<LocationListener.LocationChangeListener> locationChangeListeners= new ArrayList<>();
private Instance(Context context) { private Instance(Context context) {
db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME) db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.allowMainThreadQueries() .allowMainThreadQueries()
.build(); .build();
locationListener= new LocationListener(context);
userPreferences= new UserPreferences(); userPreferences= new UserPreferences();
} }
} }

View File

@ -20,8 +20,10 @@
package de.tadris.fitness.activity; package de.tadris.fitness.activity;
import android.Manifest; import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.location.Location; import android.location.Location;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.PowerManager; import android.os.PowerManager;
@ -66,6 +68,7 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
boolean isResumed= false; boolean isResumed= false;
private Handler mHandler= new Handler(); private Handler mHandler= new Handler();
PowerManager.WakeLock wakeLock; PowerManager.WakeLock wakeLock;
Intent locationListener;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -92,11 +95,16 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
startUpdater(); startUpdater();
acquireWakelock(); acquireWakelock();
Instance.getInstance(this).locationChangeListeners.add(this);
startListener();
} }
private void acquireWakelock(){ private void acquireWakelock(){
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "de.tadris.fitotrack:workout_recorder"); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "de.tadris.fitotrack:workout_recorder");
wakeLock.acquire(1000*60*120); wakeLock.acquire(1000*60*120);
} }
@ -163,7 +171,24 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (hasPermission()) { if (hasPermission()) {
Instance.getInstance(this).locationListener.enableMyLocation(); startListener();
}
}
public void stopListener(){
stopService(locationListener);
}
public void startListener(){
if(locationListener == null){
locationListener= new Intent(this, LocationListener.class);
}else{
stopListener();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(locationListener);
}else{
startService(locationListener);
} }
} }
@ -184,20 +209,20 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
if(wakeLock.isHeld()){ if(wakeLock.isHeld()){
wakeLock.release(); wakeLock.release();
} }
Instance.getInstance(this).locationChangeListeners.remove(this);
stopListener();
} }
@Override @Override
public void onPause(){ public void onPause(){
super.onPause(); super.onPause();
downloadLayer.onPause(); downloadLayer.onPause();
Instance.getInstance(this).locationListener.unregisterLocationChangeListeners(this);
isResumed= false; isResumed= false;
} }
public void onResume(){ public void onResume(){
super.onResume(); super.onResume();
downloadLayer.onResume(); downloadLayer.onResume();
Instance.getInstance(this).locationListener.registerLocationChangeListeners(this);
isResumed= true; isResumed= true;
} }

View File

@ -20,6 +20,7 @@
package de.tadris.fitness.data; package de.tadris.fitness.data;
import android.content.Context; import android.content.Context;
import android.util.Log;
import java.util.List; import java.util.List;
@ -34,6 +35,16 @@ public class WorkoutManager {
workout.id= System.currentTimeMillis(); workout.id= System.currentTimeMillis();
// Delete Samples with same time
for(int i= samples.size()-2; i >= 0; i--){
WorkoutSample sample= samples.get(i);
WorkoutSample lastSample= samples.get(i+1);
if(sample.absoluteTime == lastSample.absoluteTime){
samples.remove(lastSample);
Log.i("WorkoutManager", "Removed samples at " + sample.absoluteTime + " rel: " + sample.relativeTime + "; " + lastSample.relativeTime);
}
}
// Calculating values // Calculating values
double length= 0; double length= 0;
for(int i= 1; i < samples.size(); i++){ for(int i= 1; i < samples.size(); i++){

View File

@ -1,3 +1,4 @@
/* /*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> * Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
* *
@ -19,23 +20,23 @@
package de.tadris.fitness.location; package de.tadris.fitness.location;
import android.Manifest; import android.app.Notification;
import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.Intent;
import android.location.Location; import android.location.Location;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import androidx.core.app.ActivityCompat; import android.util.Log;
import org.mapsforge.core.model.LatLong; import org.mapsforge.core.model.LatLong;
import java.util.ArrayList; import de.tadris.fitness.Instance;
import java.util.List; import de.tadris.fitness.R;
import de.tadris.fitness.util.NotificationHelper;
public class LocationListener implements android.location.LocationListener { public class LocationListener extends Service {
public static LatLong static_lastLocation;
/** /**
* @param location the location whose geographical coordinates should be converted. * @param location the location whose geographical coordinates should be converted.
@ -45,113 +46,95 @@ public class LocationListener implements android.location.LocationListener {
return new LatLong(location.getLatitude(), location.getLongitude()); return new LatLong(location.getLatitude(), location.getLongitude());
} }
private Context activity; private static final String TAG = "LocationListener";
private Location lastLocation; private LocationManager mLocationManager = null;
private final LocationManager locationManager; private static final int LOCATION_INTERVAL = 1000;
private boolean myLocationEnabled;
public LocationListener(Context context) { private class LocationChangedListener implements android.location.LocationListener {
super(); Location mLastLocation;
this.activity= context;
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
public LocationChangedListener(String provider) {
/** Log.i(TAG, "LocationListener " + provider);
* Stops the receiving of location updates. Has no effect if location updates are already disabled. mLastLocation = new Location(provider);
*/
public synchronized void disableMyLocation() {
if (this.myLocationEnabled) {
this.myLocationEnabled = false;
try {
this.locationManager.removeUpdates(this);
} catch (RuntimeException runtimeException) {
// do we need to catch security exceptions for this call on Android 6?
}
} }
}
public synchronized void enableMyLocation() { @Override
enableBestAvailableProvider(); public void onLocationChanged(Location location) {
} Log.i(TAG, "onLocationChanged: " + location);
mLastLocation.set(location);
/** for(LocationChangeListener listener : Instance.getInstance(getBaseContext()).locationChangeListeners){
* @return the most-recently received location fix (might be null).
*/
public synchronized Location getLastLocation() {
return this.lastLocation;
}
/**
* @return true if the receiving of location updates is currently enabled, false otherwise.
*/
public synchronized boolean isMyLocationEnabled() {
return this.myLocationEnabled;
}
@Override
public void onLocationChanged(Location location) {
synchronized (this) {
this.lastLocation = location;
LatLong latLong = locationToLatLong(location);
static_lastLocation= latLong;
for(LocationChangeListener listener : this.locationChangeListeners){
listener.onLocationChange(location); listener.onLocationChange(location);
} }
} }
}
@Override @Override
public void onProviderDisabled(String provider) { public void onProviderDisabled(String provider) {
enableBestAvailableProvider(); Log.i(TAG, "onProviderDisabled: " + provider);
}
@Override
public void onProviderEnabled(String provider) {
enableBestAvailableProvider();
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// do nothing
}
private void enableBestAvailableProvider() {
disableMyLocation();
boolean result = false;
for (String provider : this.locationManager.getProviders(true)) {
if (LocationManager.GPS_PROVIDER.equals(provider)) {
result = true;
if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
this.locationManager.requestLocationUpdates(provider, 0, 0, this);
Location location= this.locationManager.getLastKnownLocation(provider);
if(location != null){
onLocationChanged(location);
}
}
} }
this.myLocationEnabled = result;
}
private List<LocationChangeListener> locationChangeListeners= new ArrayList<>(); @Override
public void onProviderEnabled(String provider) {
public void registerLocationChangeListeners(LocationChangeListener listener){ Log.i(TAG, "onProviderEnabled: " + provider);
if(locationChangeListeners.size() == 0){ }
enableMyLocation();
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.i(TAG, "onStatusChanged: " + provider);
} }
locationChangeListeners.add(listener);
} }
public void unregisterLocationChangeListeners(LocationChangeListener listener){ LocationChangedListener gpsListener= new LocationChangedListener(LocationManager.GPS_PROVIDER);
locationChangeListeners.remove(listener);
if(locationChangeListeners.size() == 0){ @Override
disableMyLocation(); public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand");
super.onStartCommand(intent, flags, startId);
Notification.Builder builder = new Notification.Builder(this)
.setContentTitle(getText(R.string.trackerRunning))
.setContentText(getText(R.string.trackerRunningMessage));
//.setSmallIcon(R.drawable.icon)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationHelper.createChannels(this);
builder.setChannelId(NotificationHelper.CHANNEL_WORKOUT);
}
startForeground(10, builder.build());
return START_STICKY;
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate");
initializeLocationManager();
try {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, LOCATION_INTERVAL, 0, gpsListener);
} catch (java.lang.SecurityException ex) {
Log.i(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "gps provider does not exist " + ex.getMessage());
}
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy");
super.onDestroy();
if (mLocationManager != null) {
mLocationManager.removeUpdates(gpsListener);
}
}
private void initializeLocationManager() {
Log.i(TAG, "initializeLocationManager");
if (mLocationManager == null) {
mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
} }
} }

View File

@ -73,7 +73,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
Log.i("Recorder", "Start"); Log.i("Recorder", "Start");
workout.start= System.currentTimeMillis(); workout.start= System.currentTimeMillis();
resume(); resume();
Instance.getInstance(context).locationListener.registerLocationChangeListeners(this); Instance.getInstance(context).locationChangeListeners.add(this);
startWatchdog(); startWatchdog();
}else if(state == RecordingState.PAUSED){ }else if(state == RecordingState.PAUSED){
resume(); resume();
@ -143,7 +143,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
workout.duration= time; workout.duration= time;
workout.pauseDuration= pauseTime; workout.pauseDuration= pauseTime;
state= RecordingState.STOPPED; state= RecordingState.STOPPED;
Instance.getInstance(context).locationListener.unregisterLocationChangeListeners(this); Instance.getInstance(context).locationChangeListeners.remove(this);
} }
public void save(){ public void save(){

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.util;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import de.tadris.fitness.R;
public class NotificationHelper {
public static String CHANNEL_WORKOUT= "workout";
public static void createChannels(Context context){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(context, CHANNEL_WORKOUT, R.string.trackingInfo, R.string.trackingInfoDescription, NotificationManager.IMPORTANCE_LOW);
}
}
private static void createNotificationChannel(Context context, String id, int nameId, int descriptionId, int importance) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = context.getString(nameId);
String description = context.getString(descriptionId);
NotificationChannel channel = new NotificationChannel(id, name, importance);
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
}

View File

@ -42,5 +42,11 @@
<string name="deleteWorkout">Delete Workout</string> <string name="deleteWorkout">Delete Workout</string>
<string name="deleteWorkoutMessage">Do you really want to delete the workout?</string> <string name="deleteWorkoutMessage">Do you really want to delete the workout?</string>
<string name="trackerRunning">Tracker is running</string>
<string name="trackerRunningMessage">Your workout is being recorded</string>
<string name="trackingInfo">Tracking Info</string>
<string name="trackingInfoDescription">Info about the tracker running</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
</resources> </resources>