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

Refactor web sync to foreground Service instead of JobIntentService, fixes #113

parent 9be196a5
Loading
Loading
Loading
Loading
+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);
            WebSyncService.enqueueWork(context, intent);
            context.startService(intent);
        }
    }
}
+11 −65
Original line number Diff line number Diff line
@@ -9,18 +9,17 @@

package net.fabiszewski.ulogger;

import static net.fabiszewski.ulogger.LoggerTask.E_DISABLED;
import static net.fabiszewski.ulogger.LoggerTask.E_PERMISSION;
import static net.fabiszewski.ulogger.MainActivity.UPDATED_PREFS;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -28,15 +27,8 @@ import android.os.Looper;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.TaskStackBuilder;
import androidx.preference.PreferenceManager;

import static net.fabiszewski.ulogger.LoggerTask.E_DISABLED;
import static net.fabiszewski.ulogger.LoggerTask.E_PERMISSION;
import static net.fabiszewski.ulogger.MainActivity.UPDATED_PREFS;

/**
 * Background service logging positions to database
 * and synchronizing with remote server.
@@ -66,8 +58,7 @@ public class LoggerService extends Service {

    private static Location lastLocation = null;

    private final int NOTIFICATION_ID = 1526756640;
    private NotificationManager notificationManager;
    private NotificationHelper notificationHelper;

    /**
     * Basic initializations
@@ -77,13 +68,9 @@ public class LoggerService extends Service {
    public void onCreate() {
        if (Logger.DEBUG) { Log.d(TAG, "[onCreate]"); }

        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager != null) {
            notificationManager.cancelAll();
        }

        locationHelper = LocationHelper.getInstance(this);
        locationListener = new mLocationListener();
        notificationHelper = new NotificationHelper(this);

        thread = new HandlerThread("LoggerThread");
        thread.start();
@@ -110,7 +97,7 @@ public class LoggerService extends Service {
            syncIntent = new Intent(getApplicationContext(), WebSyncService.class);

            if (locationHelper.isLiveSync() && DbAccess.needsSync(this)) {
                WebSyncService.enqueueWork(getApplicationContext(), syncIntent);
                getApplicationContext().startService(syncIntent);
            }
            return true;
        } catch (LocationHelper.LoggerException e) {
@@ -139,8 +126,8 @@ public class LoggerService extends Service {
        if (intent != null && intent.getBooleanExtra(UPDATED_PREFS, false)) {
            handlePrefsUpdated();
        } else {
            final Notification notification = showNotification(NOTIFICATION_ID);
            startForeground(NOTIFICATION_ID, notification);
            final Notification notification = notificationHelper.showNotification();
            startForeground(notificationHelper.getId(), notification);
            if (!initializeLocationUpdates()) {
                setRunning(false);
                stopSelf();
@@ -191,7 +178,7 @@ public class LoggerService extends Service {

        setRunning(false);

        notificationManager.cancel(NOTIFICATION_ID);
        notificationHelper.cancelNotification();
        sendBroadcast(BROADCAST_LOCATION_STOPPED);

        if (thread != null) {
@@ -245,47 +232,6 @@ public class LoggerService extends Service {
        lastLocation = null;
    }

    /**
     * Show notification
     * @param mId Notification Id
     */
    @SuppressWarnings("SameParameterValue")
    private Notification showNotification(int mId) {
        if (Logger.DEBUG) { Log.d(TAG, "[showNotification " + mId + "]"); }

        final String channelId = String.valueOf(mId);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel(channelId);
        }
        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(this, channelId)
                        .setSmallIcon(R.drawable.ic_stat_notify_24dp)
                        .setContentTitle(getString(R.string.app_name))
                        .setPriority(NotificationCompat.PRIORITY_HIGH)
                        .setCategory(NotificationCompat.CATEGORY_SERVICE)
                        .setContentText(String.format(getString(R.string.is_running), getString(R.string.app_name)));
        Intent resultIntent = new Intent(this, MainActivity.class);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.class);
        stackBuilder.addNextIntent(resultIntent);
        PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        mBuilder.setContentIntent(resultPendingIntent);
        Notification mNotification = mBuilder.build();
        notificationManager.notify(mId, mNotification);
        return mNotification;
    }

    /**
     * Create notification channel
     * @param channelId Channel Id
     */
    @RequiresApi(Build.VERSION_CODES.O)
    private void createNotificationChannel(String channelId) {
        NotificationChannel chan = new NotificationChannel(channelId, getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH);
        notificationManager.createNotificationChannel(chan);
    }

    /**
     * Send broadcast message
     * @param broadcast Broadcast message
@@ -315,7 +261,7 @@ public class LoggerService extends Service {
                DbAccess.writeLocation(LoggerService.this, location);
                sendBroadcast(BROADCAST_LOCATION_UPDATED);
                if (locationHelper.isLiveSync()) {
                    WebSyncService.enqueueWork(getApplicationContext(), syncIntent);
                    getApplicationContext().startService(syncIntent);
                }
            }
        }
+4 −4
Original line number Diff line number Diff line
@@ -9,6 +9,9 @@

package net.fabiszewski.ulogger;

import static net.fabiszewski.ulogger.Alert.showAlert;
import static net.fabiszewski.ulogger.Alert.showConfirm;

import android.Manifest;
import android.app.Activity;
import android.content.BroadcastReceiver;
@@ -49,9 +52,6 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import static net.fabiszewski.ulogger.Alert.showAlert;
import static net.fabiszewski.ulogger.Alert.showConfirm;

@SuppressWarnings("WeakerAccess")
public class MainFragment extends Fragment {

@@ -297,7 +297,7 @@ public class MainFragment extends Fragment {
            showToast(getString(R.string.provide_user_pass_url));
        } else if (DbAccess.needsSync(context)) {
            Intent syncIntent = new Intent(context, WebSyncService.class);
            WebSyncService.enqueueWork(context, syncIntent);
            context.startService(syncIntent);
            showToast(getString(R.string.uploading_started));
            isUploading = true;
        } else {
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2021 Bartek Fabiszewski
 * http://www.fabiszewski.net
 *
 * This file is part of μlogger-android.
 * Licensed under GPL, either version 3, or any later.
 * See <http://www.gnu.org/licenses/>
 */

package net.fabiszewski.ulogger;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;

import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.TaskStackBuilder;

class NotificationHelper {

    private static final String TAG = NotificationHelper.class.getSimpleName();
    private final int NOTIFICATION_ID = 1526756640;
    private final NotificationManager notificationManager;
    private final Context context;

    /**
     * Constructor
     * @param ctx Context
     */
    NotificationHelper(Context ctx) {
        context = ctx;
        notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager != null) {
            notificationManager.cancelAll();
        }
    }

    /**
     * Show notification
     * @param mId Notification Id
     */
    Notification showNotification() {
        if (Logger.DEBUG) { Log.d(TAG, "[showNotification " + NOTIFICATION_ID + "]"); }

        final String channelId = String.valueOf(NOTIFICATION_ID);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel(channelId);
        }
        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(context, channelId)
                        .setSmallIcon(R.drawable.ic_stat_notify_24dp)
                        .setContentTitle(context.getString(R.string.app_name))
                        .setPriority(NotificationCompat.PRIORITY_HIGH)
                        .setCategory(NotificationCompat.CATEGORY_SERVICE)
                        .setContentText(String.format(context.getString(R.string.is_running), context.getString(R.string.app_name)));
        Intent resultIntent = new Intent(context, MainActivity.class);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
        stackBuilder.addParentStack(MainActivity.class);
        stackBuilder.addNextIntent(resultIntent);
        PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        mBuilder.setContentIntent(resultPendingIntent);
        Notification mNotification = mBuilder.build();
        notificationManager.notify(NOTIFICATION_ID, mNotification);
        return mNotification;
    }

    /**
     * Create notification channel
     * @param channelId Channel Id
     */
    @RequiresApi(Build.VERSION_CODES.O)
    private void createNotificationChannel(String channelId) {
        NotificationChannel channel = new NotificationChannel(channelId, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH);
        notificationManager.createNotificationChannel(channel);
    }

    /**
     * Cancel notification
     */
    void cancelNotification() {
        notificationManager.cancel(NOTIFICATION_ID);
    }

    /**
     * Get notification ID
     * @return Notification ID
     */
    int getId() {
        return NOTIFICATION_ID;
    }
}
+88 −29
Original line number Diff line number Diff line
@@ -9,17 +9,27 @@

package net.fabiszewski.ulogger;

import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

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

import org.json.JSONException;

@@ -34,49 +44,56 @@ import java.util.HashMap;
import java.util.Map;

/**
 * Service synchronizing local database positions with remote server.
 *
 * Service synchronizing local database positions with remote server
 */

public class WebSyncService extends JobIntentService {
public class WebSyncService extends Service {

    private static final String TAG = WebSyncService.class.getSimpleName();
    public static final String BROADCAST_SYNC_FAILED = "net.fabiszewski.ulogger.broadcast.sync_failed";
    public static final String BROADCAST_SYNC_DONE = "net.fabiszewski.ulogger.broadcast.sync_done";

    private HandlerThread thread;
    private ServiceHandler serviceHandler;
    private DbAccess db;
    private WebHelper web;
    private static PendingIntent pi = null;

    final private static int FIVE_MINUTES = 1000 * 60 * 5;

    static final int JOB_ID = 1001;
    private NotificationHelper notificationHelper;

    /**
     * Convenience method for enqueuing work in to this service.
     * Basic initializations
     * Start looper to process uploads
     */
    static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, WebSyncService.class, JOB_ID, work);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (Logger.DEBUG) { Log.d(TAG, "[websync create]"); }

        web = new WebHelper(this);
        notificationHelper = new NotificationHelper(this);

        thread = new HandlerThread("WebSyncThread", THREAD_PRIORITY_BACKGROUND);
        thread.start();
        Looper looper = thread.getLooper();
        serviceHandler = new ServiceHandler(looper);

        // keep database open during whole service runtime
        db = DbAccess.getInstance();
        db.open(this);
    }

    /**
     * Handle synchronization intent
     * @param intent Intent
     * Handler to do synchronization on background thread
     */
    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        if (Logger.DEBUG) { Log.d(TAG, "[websync start]"); }
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            cancelPending();

            if (!WebHelper.isAuthorized) {
@@ -84,6 +101,7 @@ public class WebSyncService extends JobIntentService {
                    web.authorize();
                } catch (WebAuthException|IOException|JSONException e) {
                    handleError(e);
                    stopSelf(msg.arg1);
                    return;
                }
            }
@@ -93,6 +111,31 @@ public class WebSyncService extends JobIntentService {
            if (trackId > 0) {
                doSync(trackId);
            }

            stopSelf(msg.arg1);
        }
    }

    /**
     * Start foreground service
     *
     * @param intent Intent
     * @param flags Flags
     * @param startId Unique id
     * @return Always returns START_STICKY
     */
    @Override
    public int onStartCommand(@NonNull Intent intent, int flags, int startId) {
        if (Logger.DEBUG) { Log.d(TAG, "[websync start]"); }

        final Notification notification = notificationHelper.showNotification();
        startForeground(notificationHelper.getId(), notification);

        Message msg = serviceHandler.obtainMessage();
        msg.arg1 = startId;
        serviceHandler.sendMessage(msg);

        return START_STICKY;
    }

    /**
@@ -212,7 +255,11 @@ public class WebSyncService extends JobIntentService {
        if (Logger.DEBUG) { Log.d(TAG, "[websync set alarm]"); }
        AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent syncIntent = new Intent(getApplicationContext(), WebSyncService.class);
        pi = PendingIntent.getService(this, 0, syncIntent, FLAG_ONE_SHOT);
        int flags = FLAG_ONE_SHOT;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            flags |= FLAG_IMMUTABLE;
        }
        pi = PendingIntent.getService(this, 0, syncIntent, flags);
        if (am != null) {
            am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + FIVE_MINUTES, pi);
        }
@@ -284,7 +331,19 @@ public class WebSyncService extends JobIntentService {
        if (db != null) {
            db.close();
        }
        super.onDestroy();
        notificationHelper.cancelNotification();

        if (thread != null) {
            thread.interrupt();
            thread = null;
        }
    }


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not implemented");
    }

}