Skip to content

Commit

Permalink
Tracking service: initial prototype with fused location
Browse files Browse the repository at this point in the history
  • Loading branch information
wonder-sk committed Jun 11, 2024
1 parent c1b96cd commit 70351b3
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 502 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ if (DEFINED ENV{INPUT_SDK_ANDROID_BASE})
set(ANDROID_STL "c++_shared")

# Target/Minimum API levels for Android, used as Input target properties
set(INPUT_ANDROID_TARGET_SDK_VERSION "33")
set(INPUT_ANDROID_TARGET_SDK_VERSION "34")
set(INPUT_ANDROID_MIN_SDK_VERSION "${ANDROIDAPI}")
set(INPUT_ANDROID_NDK_PATH "$ENV{ANDROID_NDK_ROOT}")
if (NOT INPUT_ANDROID_NDK_PATH)
Expand Down
2 changes: 0 additions & 2 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,12 @@ if (ANDROID)
set(MM_HDRS
${MM_HDRS}
position/tracking/androidtrackingbackend.h
position/tracking/androidtrackingbroadcast.h
position/providers/androidpositionprovider.h
)

set(MM_SRCS
${MM_SRCS}
position/tracking/androidtrackingbackend.cpp
position/tracking/androidtrackingbroadcast.cpp
position/providers/androidpositionprovider.cpp
)
endif ()
Expand Down
11 changes: 3 additions & 8 deletions app/activeproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@
#include "activeproject.h"
#include "coreutils.h"

#ifdef ANDROID
#include "position/tracking/androidtrackingbroadcast.h"
#endif

const QString ActiveProject::LOADING_FLAG_FILE_PATH = QString( "%1/.input_loading_project" ).arg( QStandardPaths::standardLocations( QStandardPaths::TempLocation ).first() );

Expand Down Expand Up @@ -105,11 +102,7 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force )
// clear autosync
setAutosyncEnabled( false );

// clear position tracking broadcast listeners
#ifdef ANDROID
disconnect( &AndroidTrackingBroadcast::getInstance() );
AndroidTrackingBroadcast::unregisterBroadcast();
#endif
// TODO: stop tracking here if it is running?

// Just clear project if empty
if ( filePath.isEmpty() )
Expand Down Expand Up @@ -222,6 +215,7 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force )
}

// in case tracking is running, we want to show the UI
/*
#ifdef ANDROID
if ( positionTrackingSupported() )
{
Expand All @@ -244,6 +238,7 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force )
AndroidTrackingBroadcast::sendAliveRequestAsync();
}
#endif
*/

return res;
}
Expand Down
180 changes: 37 additions & 143 deletions app/android/src/uk/co/lutraconsulting/PositionTrackingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import android.content.Intent;
import android.app.PendingIntent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.util.Log;

import android.app.Notification;
import android.app.NotificationChannel;
Expand All @@ -24,56 +26,49 @@
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationListener;
import android.location.GnssStatus;

import androidx.annotation.NonNull;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;

import uk.co.lutraconsulting.PositionTrackingBroadcastMiddleware;
import uk.co.lutraconsulting.MMAndroidPosition;

import java.util.Timer;
import java.util.TimerTask;

public class PositionTrackingService extends Service implements LocationListener {
public class PositionTrackingService extends Service {

private static final String TAG = "PositionTrackingService";

// Channel for notifications
public static final String CHANNEL_ID = "PositionTrackingServiceChannel";

public static final int SERVICE_ID = 1010;

private MMAndroidPosition mAndroidPos = null;

Location loc;
double latitude;
double longitude;

private static long MIN_DISTANCE_CHANGE_FOR_UPDATES = 1;
private static long MIN_TIME_BW_UPDATES = 1000; //ms

protected LocationManager locationManager;
private FileOutputStream positionUpdatesStream;

public boolean amIRunning = false;

private static native void servicePositionUpdated(Location location);

@Override
public void onCreate() {
super.onCreate();

// Create the file to store position updates
File file = new File( getFilesDir(), "tracking_updates.txt" );
Log.i("CPP", "[java] [service] onCreate()");

sendStatusUpdateMessage( "Tracking file path:" + file.getAbsolutePath() );

try {
// Open the FileOutputStream in append mode
positionUpdatesStream = new FileOutputStream(file, true);
MMAndroidPosition.Callback callback = new MMAndroidPosition.Callback() {
@Override
public void onPositionChanged(@NonNull Location location, GnssStatus gnssStatus) {
Log.i("CPP", "[java] [service] new pos " + location.getLatitude() + " " + location.getLongitude());
// notify tracking backend (c++ code)
servicePositionUpdated(location);
}
};

} catch ( IOException e ) {
e.printStackTrace();
sendStatusUpdateMessage("ERROR #GENERAL: Could not open file stream: " + e.getMessage() );
}
mAndroidPos = new MMAndroidPosition(this, callback, true);
}

@Override
Expand All @@ -85,55 +80,32 @@ public IBinder onBind( Intent intent ) {
@Override
public void onDestroy() {

if (locationManager != null) {
locationManager.removeUpdates(this);
}
Log.i("CPP", "[java] [service] onDestroy()");

// Close the FileOutputStream when the service is destroyed
try {
if (positionUpdatesStream != null) {
positionUpdatesStream.close();
}
} catch (IOException e) {
e.printStackTrace();
sendStatusUpdateMessage("ERROR #SILENT: Could not close file stream: " + e.getMessage() );
}
mAndroidPos.stop();

super.onDestroy();
}

@Override
public void onTaskRemoved(Intent rootIntent){
Log.i("CPP", "[java] [service] onTaskRemoved()");

// this does not seem to be called

public void sendStatusUpdateMessage( String message ) {
Intent sendToBroadcastIntent = new Intent();

sendToBroadcastIntent.setAction( PositionTrackingBroadcastMiddleware.TRACKING_STATUS_MESSAGE_ACTION );
sendToBroadcastIntent.putExtra( PositionTrackingBroadcastMiddleware.TRACKING_STATUS_MESSAGE_TAG, message );

sendBroadcast( sendToBroadcastIntent );
}

public void sendAliveStatusResponse( boolean isAlive ) {
Intent sendToBroadcastIntent = new Intent();

sendToBroadcastIntent.setAction( PositionTrackingBroadcastMiddleware.TRACKING_ALIVE_STATUS_ACTION );
sendToBroadcastIntent.putExtra( PositionTrackingBroadcastMiddleware.TRACKING_ALIVE_STATUS_TAG, isAlive );

sendBroadcast( sendToBroadcastIntent );
// TODO: maybe restart with AlarmManager?
// https://stackoverflow.com/questions/26842675/continue-service-even-if-application-is-cleared-from-recent-app

super.onTaskRemoved(rootIntent);
}

@Override
public int onStartCommand( Intent intent, int flags, int startId ) {

if ( intent.hasExtra( PositionTrackingBroadcastMiddleware.TRACKING_ALIVE_STATUS_ACTION ) ) {
// we are just checking if the service is running, without intention to start it
sendStatusUpdateMessage("Responding to alive request from service!!");
sendAliveStatusResponse( amIRunning );
stopSelf();

return START_NOT_STICKY; // do not bother recreating it
}
Log.i("CPP", "[java] [service] onStartCommand()");

if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.O ) {
sendStatusUpdateMessage( "ERROR #UNSUPPORTED: tracking is not supported on your Android version ( Android O (8.0) required )" );
Log.e("CPP", "ERROR #UNSUPPORTED: tracking is not supported on your Android version ( Android O (8.0) required )" );
stopSelf();

return START_NOT_STICKY; // do not bother recreating it
Expand Down Expand Up @@ -166,90 +138,12 @@ public int onStartCommand( Intent intent, int flags, int startId ) {

Notification notification = notificationBuilder.build();

startForeground( SERVICE_ID, notification );

sendStatusUpdateMessage( "Position tracking: Started the foreground service!" );
// TODO: use ServiceCompat? (this variant of startForeground needs Android >= 10 (API level 29)
startForeground( SERVICE_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION );

locationManager = ( LocationManager ) getApplication().getSystemService( LOCATION_SERVICE );

if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.O ) {
sendStatusUpdateMessage( "ERROR #UNSUPPORTED: tracking is not supported on your Android version ( Android O (8.0) required )" );
stopSelf();

return START_NOT_STICKY; // do not bother recreating it
}

String positionProvider;

// FUSED_PROVIDER is available since API 31 (Android 12)
if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) {
positionProvider = LocationManager.GPS_PROVIDER;
}
else {
positionProvider = LocationManager.FUSED_PROVIDER;
}

boolean isGPSAvailable = locationManager.isProviderEnabled( positionProvider );
if ( !isGPSAvailable ) {
sendStatusUpdateMessage( "ERROR #GPS_UNAVAILABLE: GPS is not available!" );
stopSelf();
stopForeground( true );
}
else {
boolean fineLocationAccessGranted = checkSelfPermission( Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED;
boolean coarseLocationAccessGranted = checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;

if ( !fineLocationAccessGranted || !coarseLocationAccessGranted ) {
sendStatusUpdateMessage( "ERROR #PERMISSIONS: missing location permissions!" );
stopSelf();
stopForeground(true);
}

long timeInterval = MIN_TIME_BW_UPDATES;
long distanceInterval = MIN_DISTANCE_CHANGE_FOR_UPDATES;

if ( intent.hasExtra( "uk.co.lutraconsulting.tracking.timeInterval" ) ) {
timeInterval = (long) intent.getDoubleExtra( "uk.co.lutraconsulting.tracking.timeInterval", 1000 );
}

if ( intent.hasExtra( "uk.co.lutraconsulting.tracking.distanceInterval" ) ) {
distanceInterval = (long) intent.getDoubleExtra( "uk.co.lutraconsulting.tracking.distanceInterval", 1 );
}

locationManager.requestLocationUpdates(
positionProvider,
timeInterval,
distanceInterval,
this
);

sendStatusUpdateMessage( "Started to listen to position updates!" );
}

amIRunning = true;
mAndroidPos.start();

return START_STICKY;
}

@Override
public void onLocationChanged( Location location ) {

long currentTimeSeconds = System.currentTimeMillis() / 1000; // UTC time
String positionUpdate = String.format(Locale.US, "%f %f %f %d\n", location.getLongitude(), location.getLatitude(), location.getAltitude(), currentTimeSeconds);

try {
if ( positionUpdatesStream != null ) {
positionUpdatesStream.write( positionUpdate.getBytes() );
}
} catch ( IOException e ) {
e.printStackTrace();
sendStatusUpdateMessage("ERROR #GENERAL: Could not write to file:" + e.getMessage() );
}

// notify cpp about position update
Intent sendToBroadcastIntent = new Intent();

sendToBroadcastIntent.setAction( PositionTrackingBroadcastMiddleware.TRACKING_POSITION_UPDATE_ACTION );
sendBroadcast( sendToBroadcastIntent );
}
}
Loading

1 comment on commit 70351b3

@inputapp-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iOS - version 24.6.632011 just submitted!

Please sign in to comment.