Commit ad3c3ae4 authored by Bartek Fabiszewski's avatar Bartek Fabiszewski
Browse files

Refactor GPX export service as Runnable

parent e678fb6f
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -58,11 +58,6 @@
            android:foregroundServiceType="location" />
        <service
            android:name=".WebSyncService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false" />
        <service
            android:name=".GpxExportService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false" />

        <receiver
+1 −1
Original line number Diff line number Diff line
@@ -88,7 +88,7 @@ public class ExternalCommandReceiver extends BroadcastReceiver {
    private void uploadData(Context context) {
        if (DbAccess.needsSync(context)) {
            Intent intent = new Intent(context, WebSyncService.class);
            context.startService(intent);
            ContextCompat.startForegroundService(context, intent);
        }
    }
}
+95 −48
Original line number Diff line number Diff line
@@ -9,31 +9,33 @@

package net.fabiszewski.ulogger;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Xml;

import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;

import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;

/**
 * Export track to GPX format
 */
public class GpxExportService extends JobIntentService {
public class GpxExportTask implements Runnable {

    private static final String TAG = GpxExportService.class.getSimpleName();

    public static final String BROADCAST_EXPORT_FAILED = "net.fabiszewski.ulogger.broadcast.write_failed";
    public static final String BROADCAST_EXPORT_DONE = "net.fabiszewski.ulogger.broadcast.write_ok";
    private static final String TAG = GpxExportTask.class.getSimpleName();

    private static final String ns_gpx = "http://www.topografix.com/GPX/1/1";
    private static final String ns_ulogger = "https://github.com/bfabiszewski/ulogger-android/1";
@@ -46,48 +48,84 @@ public class GpxExportService extends JobIntentService {

    private DbAccess db;

    static final int JOB_ID = 1000;
    private final WeakReference<GpxExportTaskCallback> weakCallback;

    private String errorMessage = "";
    private final Uri uri;

    private boolean isRunning = false;

    private final Handler uiHandler = new Handler(Looper.getMainLooper());

    /**
     * Convenience method for enqueuing work in to this service.
     * @param uri URI to exported file
     * @param callback Callback activity
     */
    static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, GpxExportService.class, JOB_ID, work);
    GpxExportTask(@NonNull Uri uri, @NonNull GpxExportTaskCallback callback) {
        this.uri = uri;
        weakCallback = new WeakReference<>(callback);
    }

    /**
     * Runnable actions
     */
    @Override
    public void onCreate() {
        super.onCreate();
        if (Logger.DEBUG) { Log.d(TAG, "[gpx export create]"); }
    public void run() {
        isRunning = true;
        boolean result = doInBackground();
        uiHandler.post(() -> onPostExecute(result));
        isRunning = false;
    }

        db = DbAccess.getOpenInstance(this);
    /**
     * Check whether task is running
     * @return True if running
     */
    public boolean isRunning() {
        return isRunning;
    }

    /**
     * Cleanup
     * Actions to run on worker thread
     * @return True on success
     */
    @Override
    public void onDestroy() {
    @WorkerThread
    private boolean doInBackground() {
        if (Logger.DEBUG) { Log.d(TAG, "[doInBackground]"); }
        try {
            Activity activity = getActivity();
            if (activity == null) {
                return false;
            }
            Context context = activity.getApplicationContext();
            if (Logger.DEBUG) { Log.d(TAG, "[gpx export start]"); }
            db = DbAccess.getOpenInstance(context);
            write(context, this.uri);
            if (Logger.DEBUG) { Log.d(TAG, "[gpx export stop]"); }
            if (db != null) {
                db.close();
            }
        super.onDestroy();
        } catch (IOException e) {
            if (e.getMessage() != null) {
                errorMessage = e.getMessage();
            }
            return false;
        }
        return true;
    }

    /**
     * Handle intent
     *
     * @param intent Intent
     * Post execution actions
     * @param isSuccess Result of task, true if successful
     */
    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        if (intent.getData() != null) {
            try {
                write(intent.getData());
                sendBroadcast(BROADCAST_EXPORT_DONE, null);
            } catch (IOException e) {
                sendBroadcast(BROADCAST_EXPORT_FAILED, e.getMessage());
    @UiThread
    private void onPostExecute(boolean isSuccess) {
        GpxExportTaskCallback callback = weakCallback.get();
        if (callback != null && callback.getActivity() != null) {
            if (isSuccess) {
                callback.onGpxExportTaskCompleted();
            } else {
                callback.onGpxExportTaskFailure(errorMessage);
            }
        }
    }
@@ -97,13 +135,13 @@ public class GpxExportService extends JobIntentService {
     * @param uri Target URI
     * @throws IOException Exception
     */
    private void write(@NonNull Uri uri) throws IOException {
        OutputStream stream = getContentResolver().openOutputStream(uri);
    private void write(@NonNull Context context, @NonNull Uri uri) throws IOException {
        OutputStream stream = context.getContentResolver().openOutputStream(uri);
        if (stream == null) {
            throw new IOException(getString(R.string.e_open_out_stream));
            throw new IOException(context.getString(R.string.e_open_out_stream));
        }
        try (BufferedOutputStream bufferedStream = new BufferedOutputStream(stream)) {
            serialize(bufferedStream);
            serialize(context, bufferedStream);
            if (Logger.DEBUG) { Log.d(TAG, "[export gpx file written to " + uri); }
        } catch (IOException|IllegalArgumentException|IllegalStateException e) {
            if (Logger.DEBUG) { Log.d(TAG, "[export gpx write exception: " + e + "]"); }
@@ -116,7 +154,7 @@ public class GpxExportService extends JobIntentService {
     * @param stream Output stream
     * @throws IOException Exception
     */
    private void serialize(@NonNull OutputStream stream) throws IOException {
    private void serialize(@NonNull Context context, @NonNull OutputStream stream) throws IOException {
        XmlSerializer serializer = Xml.newSerializer();
        serializer.setOutput(stream, "UTF-8");

@@ -129,13 +167,13 @@ public class GpxExportService extends JobIntentService {
        serializer.attribute(null, "xmlns", ns_gpx);
        serializer.attribute(ns_xsi, "schemaLocation", schemaLocation);
        serializer.attribute(null, "version", "1.1");
        String creator = getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME;
        String creator = context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME;
        serializer.attribute(null, "creator", creator);

        // metadata
        String trackName = db.getTrackName();
        if (trackName == null) {
            trackName = getString(R.string.unknown_track);
            trackName = context.getString(R.string.unknown_track);
        }
        long trackTimestamp = db.getFirstTimestamp();
        String trackTime = DbAccess.getTimeISO8601(trackTimestamp);
@@ -237,16 +275,25 @@ public class GpxExportService extends JobIntentService {
    }

    /**
     * Send broadcast message
     * @param broadcast Broadcast intent
     * @param message Optional extra message
     * Get activity from callback
     * @return Activity, null if not available
     */
    private void sendBroadcast(String broadcast, String message) {
        Intent intent = new Intent(broadcast);
        if (message != null) {
            intent.putExtra("message", message);
    @Nullable
    private Activity getActivity() {
        GpxExportTaskCallback callback = weakCallback.get();
        if (callback != null) {
            return callback.getActivity();
        }
        return null;
    }
        sendBroadcast(intent);

    /**
     * Callback interface
     */
    interface GpxExportTaskCallback {
        void onGpxExportTaskCompleted();
        void onGpxExportTaskFailure(@NonNull String error);
        Activity getActivity();
    }

}
+42 −10
Original line number Diff line number Diff line
@@ -9,11 +9,18 @@

package net.fabiszewski.ulogger;

import static net.fabiszewski.ulogger.Alert.showAlert;
import static net.fabiszewski.ulogger.Alert.showConfirm;
import static net.fabiszewski.ulogger.GpxExportTask.GPX_EXTENSION;
import static net.fabiszewski.ulogger.GpxExportTask.GPX_MIME;
import static java.util.concurrent.Executors.newCachedThreadPool;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
@@ -32,10 +39,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;

import static net.fabiszewski.ulogger.Alert.showAlert;
import static net.fabiszewski.ulogger.Alert.showConfirm;
import static net.fabiszewski.ulogger.GpxExportService.GPX_EXTENSION;
import static net.fabiszewski.ulogger.GpxExportService.GPX_MIME;
import java.util.concurrent.ExecutorService;

/**
 * Main activity of ulogger
@@ -43,11 +47,11 @@ import static net.fabiszewski.ulogger.GpxExportService.GPX_MIME;
 */

public class MainActivity extends AppCompatActivity
        implements FragmentManager.OnBackStackChangedListener, MainFragment.OnFragmentInteractionListener {
        implements FragmentManager.OnBackStackChangedListener, MainFragment.OnFragmentInteractionListener,
        GpxExportTask.GpxExportTaskCallback {

    private final String TAG = MainActivity.class.getSimpleName();


    private final static int RESULT_PREFS_UPDATED = 1;
    private final static int RESULT_GPX_EXPORT = 2;
    public final static String UPDATED_PREFS = "extra_updated_prefs";
@@ -56,6 +60,8 @@ public class MainActivity extends AppCompatActivity
    public String preferenceUnits;
    public long preferenceMinTimeMillis;
    public boolean preferenceLiveSync;
    private GpxExportTask gpxExportTask;
    private final ExecutorService executor = newCachedThreadPool();

    private DbAccess db;

@@ -192,10 +198,7 @@ public class MainActivity extends AppCompatActivity
            }
        } else if (requestCode == RESULT_GPX_EXPORT && resultCode == Activity.RESULT_OK) {
            if (data != null) {
                Intent intent = new Intent(MainActivity.this, GpxExportService.class);
                intent.setData(data.getData());
                GpxExportService.enqueueWork(MainActivity.this, intent);
                showToast(getString(R.string.export_started));
                runGpxExportTask(data.getData());
            }
        }
    }
@@ -300,4 +303,33 @@ public class MainActivity extends AppCompatActivity
    public void onBackStackChanged() {
        setHomeUpButton();
    }

    /**
     * Start GPX export task
     */
    private void runGpxExportTask(@NonNull Uri uri) {
        if (gpxExportTask == null || !gpxExportTask.isRunning()) {
            gpxExportTask = new GpxExportTask(uri, this);
            executor.execute(gpxExportTask);
            showToast(getString(R.string.export_started));
        }
    }

    @Override
    public void onGpxExportTaskCompleted() {
        showToast(getString(R.string.export_done));
    }

    @Override
    public void onGpxExportTaskFailure(@NonNull String error) {
        String message = getString(R.string.export_failed);
        message += "\n" + error;
        showToast(message);
    }

    @Override
    public Activity getActivity() {
        return this;
    }

}
+0 −13
Original line number Diff line number Diff line
@@ -629,8 +629,6 @@ public class MainFragment extends Fragment {
            filter.addAction(LoggerService.BROADCAST_LOCATION_GPS_ENABLED);
            filter.addAction(LoggerService.BROADCAST_LOCATION_NETWORK_ENABLED);
            filter.addAction(LoggerService.BROADCAST_LOCATION_PERMISSION_DENIED);
            filter.addAction(GpxExportService.BROADCAST_EXPORT_FAILED);
            filter.addAction(GpxExportService.BROADCAST_EXPORT_DONE);
            filter.addAction(WebSyncService.BROADCAST_SYNC_DONE);
            filter.addAction(WebSyncService.BROADCAST_SYNC_FAILED);
            context.registerReceiver(broadcastReceiver, filter);
@@ -710,17 +708,6 @@ public class MainFragment extends Fragment {
                case LoggerService.BROADCAST_LOCATION_GPS_ENABLED:
                    showToast(getString(R.string.using_gps));
                    break;
                case GpxExportService.BROADCAST_EXPORT_DONE:
                    showToast(getString(R.string.export_done));
                    break;
                case GpxExportService.BROADCAST_EXPORT_FAILED: {
                    String message = getString(R.string.export_failed);
                    if (intent.hasExtra("message")) {
                        message += "\n" + intent.getStringExtra("message");
                    }
                    showToast(message);
                    break;
                }
                case LoggerService.BROADCAST_LOCATION_PERMISSION_DENIED:
                    showToast(getString(R.string.location_permission_denied));
                    setLocLed(LED_RED);