Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aeedeedf authored by Vincent Bourgmayer's avatar Vincent Bourgmayer
Browse files

Merge branch '299-step-3-replace-service-binding' into 'main'

299 step 3 replace service binding

See merge request !255
parents 1d10f73b db658ae5
Loading
Loading
Loading
Loading
Loading
+2 −10
Original line number Diff line number Diff line
@@ -12,14 +12,12 @@ import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Environment;

import foundation.e.drive.FileObservers.FileEventListener;
import foundation.e.drive.FileObservers.RecursiveFileObserver;
import foundation.e.drive.database.FailedSyncPrefsManager;
import foundation.e.drive.services.SynchronizationService;
import foundation.e.drive.utils.AppConstants;
import foundation.e.drive.utils.CommonUtils;
import foundation.e.drive.utils.ReleaseTree;
@@ -58,27 +56,21 @@ public class EdriveApplication extends Application {
                    .apply();
        }

        startRecursiveFileObserver();
        FailedSyncPrefsManager.getInstance(getApplicationContext()).clearPreferences();

        final Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class);
        startService(SynchronizationServiceIntent);
    }

    /**
     * Start Recursive FileObserver if not already watching
     */
    public void startRecursiveFileObserver() {
    synchronized public void startRecursiveFileObserver() {
        if (!mFileObserver.isWatching()) {
            fileEventListener.bindToSynchronizationService();
            mFileObserver.startWatching();
            Timber.d("Started RecursiveFileObserver on root folder");
        }
    }

    public void stopRecursiveFileObserver() {
    synchronized public void stopRecursiveFileObserver() {
        mFileObserver.stopWatching();
        fileEventListener.unbindFromSynchronizationService();
        Timber.d("Stopped RecursiveFileObserver on root folder");
    }

+5 −18
Original line number Diff line number Diff line
@@ -28,8 +28,8 @@ import foundation.e.drive.database.DbHelper;
import foundation.e.drive.models.SyncRequest;
import foundation.e.drive.models.SyncedFileState;
import foundation.e.drive.models.SyncedFolder;
import foundation.e.drive.services.SynchronizationService;
import foundation.e.drive.utils.SynchronizationServiceConnection;
import foundation.e.drive.synchronization.SyncRequestCollector;
import foundation.e.drive.synchronization.SyncProxy;
import timber.log.Timber;

/**
@@ -39,7 +39,6 @@ import timber.log.Timber;
public class FileEventListener  {

    private final Context appContext;
    private final SynchronizationServiceConnection serviceConnection = new SynchronizationServiceConnection();

    public FileEventListener(@NonNull Context applicationContext) {
        Timber.tag(FileEventListener.class.getSimpleName());
@@ -108,14 +107,10 @@ public class FileEventListener {
    private void sendSyncRequestToSynchronizationService(@NonNull SyncRequest request) {
        Timber.d("Sending a SyncRequest for %s", request.getSyncedFileState().getName());

        SynchronizationService service = serviceConnection.getSynchronizationService();
        final SyncRequestCollector syncManager = SyncProxy.INSTANCE;
        syncManager.queueSyncRequest(request, appContext.getApplicationContext());
        syncManager.startSynchronization(appContext);

        if (service != null) {
            service.queueSyncRequest(request);
            service.startSynchronization();
        } else {
            Timber.d("Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService");
        }
    }

    /**
@@ -245,12 +240,4 @@ public class FileEventListener {
            this.sendSyncRequestToSynchronizationService(disableSyncingRequest);
        }
    }

    public void unbindFromSynchronizationService(){
        serviceConnection.unBind(appContext);
    }

    public void bindToSynchronizationService(){
        serviceConnection.bindService(appContext);
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@

package foundation.e.drive.receivers;

import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -18,6 +19,7 @@ import androidx.annotation.NonNull;

import foundation.e.drive.BuildConfig;
import foundation.e.drive.database.DbHelper;
import foundation.e.drive.synchronization.SyncProxy;
import foundation.e.drive.utils.AppConstants;
import foundation.e.drive.utils.CommonUtils;
import timber.log.Timber;
@@ -51,6 +53,8 @@ public class BootCompletedReceiver extends BroadcastReceiver {
                    Timber.e(exception);
                }
            }

            SyncProxy.INSTANCE.startListeningFiles((Application) context.getApplicationContext());
        }
    }

+18 −50
Original line number Diff line number Diff line
@@ -14,7 +14,6 @@ import static foundation.e.drive.utils.AppConstants.SETUP_COMPLETED;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -48,11 +47,12 @@ import foundation.e.drive.models.SyncedFileState;
import foundation.e.drive.models.SyncedFolder;
import foundation.e.drive.operations.ListFileRemoteOperation;
import foundation.e.drive.receivers.DebugCmdReceiver;
import foundation.e.drive.synchronization.SyncRequestCollector;
import foundation.e.drive.synchronization.SyncProxy;
import foundation.e.drive.utils.AppConstants;
import foundation.e.drive.utils.CommonUtils;
import foundation.e.drive.utils.DavClientProvider;
import foundation.e.drive.utils.ServiceExceptionHandler;
import foundation.e.drive.utils.SynchronizationServiceConnection;
import timber.log.Timber;

/**
@@ -62,26 +62,14 @@ import timber.log.Timber;
 * This service look for remote or looale file to synchronize
 */
public class ObserverService extends Service implements OnRemoteOperationListener{
    private final static int INTERSYNC_MINIMUM_DELAY = 900000; // min delay between two sync in ms.
    private final static int INTERSYNC_MINIMUM_DELAY = 900000; // min delay execution two sync in ms.

    private List<SyncedFolder> mSyncedFolders; //List of synced folder
    private boolean isWorking = false;
    private Account mAccount;
    private HashMap<Integer, SyncRequest> syncRequests; //integer is SyncedFileState id; Parcelable is the operation
    private SynchronizationServiceConnection synchronizationServiceConnection = new SynchronizationServiceConnection() {
        @Override
        public void onServiceConnected(@Nullable ComponentName componentName, @NonNull IBinder iBinder) {
            super.onServiceConnected(componentName, iBinder);
            final SharedPreferences prefs = getApplicationContext().getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);

            if (!checkStartCondition(prefs, forcedSync)) {
                stopSelf();
                return;
            }
    private final SyncRequestCollector syncManager = SyncProxy.INSTANCE;

            begin();
        }
    };
    private Handler handler;
    private HandlerThread handlerThread;
    // protected to avoid SyntheticAccessor
@@ -91,7 +79,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene
    @Override
    public void onDestroy(){
        Timber.v("onDestroy()");
        synchronizationServiceConnection.unBind(getApplicationContext());
        if (handlerThread != null) handlerThread.quitSafely();
        mSyncedFolders = null;
        super.onDestroy();
@@ -101,7 +88,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene
    public void onCreate() {
        super.onCreate();
        Timber.tag(ObserverService.class.getSimpleName());
        synchronizationServiceConnection.bindService(getApplicationContext());
    }

    @Override
@@ -120,6 +106,13 @@ public class ObserverService extends Service implements OnRemoteOperationListene

        this.syncRequests = new HashMap<>();

        if (!checkStartCondition(prefs, forcedSync)) {
            syncManager.startListeningFiles(getApplication());
            stopSelf();
            return START_NOT_STICKY;
        }

        begin();
        return START_STICKY;
    }

@@ -156,11 +149,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene
            return false;
        }

        if (isWorking) {
            Timber.d("ObserverService is already working");
            return false;
        }

        // Check minimum delay since last call & not forced sync
        /*@todo is it really usefull to check time beetween to start as it is started by WorkManager?
        it matters only if we want to consider forced sync */
@@ -178,12 +166,10 @@ public class ObserverService extends Service implements OnRemoteOperationListene
            return false;
        }

        final SynchronizationService service =
                synchronizationServiceConnection.getSynchronizationService();

        final boolean isSyncServicePaused = service != null && service.pauseSync();
        Timber.d("isSyncServicePaused ? %s", isSyncServicePaused);
        return isSyncServicePaused;
        final boolean startAllowed = syncManager.onPeriodicScanStart(getApplication());
        Timber.d("starting periodic scan is allowed ? %s", startAllowed);
        return startAllowed;
    }

    /* Common methods */
@@ -193,7 +179,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene
     */
    // protected to avoid SyntheticAccessor
    protected void begin(){
        this.isWorking = true;
        clearCachedFile();
        deleteOldestCrashlogs();
        startScan(true);
@@ -330,39 +315,22 @@ public class ObserverService extends Service implements OnRemoteOperationListene

        startScan(false);

        SynchronizationService service = synchronizationServiceConnection.getSynchronizationService();
        if (service != null) {
            service.unPauseSync();
        }

        if (!syncRequests.isEmpty()) {
            Timber.d("syncRequests contains %s", syncRequests.size());
            passSyncRequestsToSynchronizationService();
            syncManager.queueSyncRequests(syncRequests.values(), getApplicationContext());
            syncManager.startSynchronization(getApplicationContext());
        } else {
            Timber.i("There is no file to sync.");
            getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE)
                    .edit()
                    .putLong(AppConstants.KEY_LAST_SYNC_TIME, System.currentTimeMillis())
                    .apply();
            syncManager.startListeningFiles(getApplication());
        }

        this.isWorking = false;
        this.stopSelf();
    }

    /**
     * Send all gathered SyncRequest to SynchronizationService
     */
    private void passSyncRequestsToSynchronizationService() {
        SynchronizationService service = synchronizationServiceConnection.getSynchronizationService();
        if (service != null) {
            service.queueSyncRequests(syncRequests.values());
            service.startSynchronization();
        } else {
            Timber.e("ERROR: binding to SynchronizationService lost");
        }
    }

    /**
     * Prepare the list of files and SyncedFileState for synchronisation
     */
+27 −137
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -28,10 +27,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;

import java.io.File;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;

import foundation.e.drive.R;
import foundation.e.drive.database.DbHelper;
@@ -41,6 +37,8 @@ import foundation.e.drive.models.SyncWrapper;
import foundation.e.drive.models.SyncedFileState;
import foundation.e.drive.operations.DownloadFileOperation;
import foundation.e.drive.operations.UploadFileOperation;
import foundation.e.drive.synchronization.SyncManager;
import foundation.e.drive.synchronization.SyncProxy;
import foundation.e.drive.utils.AppConstants;
import foundation.e.drive.utils.CommonUtils;
import foundation.e.drive.utils.DavClientProvider;
@@ -51,10 +49,7 @@ import timber.log.Timber;
 * @author Vincent Bourgmayer
 */
public class SynchronizationService extends Service implements OnRemoteOperationListener, FileSyncDisabler.FileSyncDisablingListener {
    private final SynchronizationBinder binder = new SynchronizationBinder();
    private final static long maxPauseTimeInMs = 300000; //5 minutes, might need to be adapted
    private ConcurrentLinkedDeque<SyncRequest> syncRequestQueue;
    private ConcurrentHashMap<Integer, SyncWrapper> startedSync; //Integer is thread index (1 to workerAmount)
    private final SyncManager syncManager = SyncProxy.INSTANCE;

    private Account account;
    private final int workerAmount = 2;
@@ -64,7 +59,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation
    private OwnCloudClient ocClient;
    private Handler handler;
    private HandlerThread handlerThread;
    private long pauseStartTime = 0L;

    @Override
    public void onCreate() {
@@ -84,12 +78,11 @@ public class SynchronizationService extends Service implements OnRemoteOperation

        if (account == null) {
            Timber.d("No account available");
            syncManager.startListeningFiles(getApplication());
            stopSelf();
            return START_NOT_STICKY;
        }

        syncRequestQueue = new ConcurrentLinkedDeque<>();
        startedSync = new ConcurrentHashMap<>();
        threadPool = new Thread[workerAmount];

        ocClient = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext());
@@ -101,6 +94,8 @@ public class SynchronizationService extends Service implements OnRemoteOperation
        handlerThread.start();
        handler = new Handler(handlerThread.getLooper());

        startSynchronization();

        return START_REDELIVER_INTENT;
    }

@@ -113,114 +108,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(@Nullable Intent intent) {
        return binder;
    }

    /**
     * Try to pause synchronization.
     * Can only be paused if no sync is already running and no request queue is started
     * @return true if service successfully paused
     */
    public boolean pauseSync() {
        pauseStartTime = System.currentTimeMillis();

        final boolean isQueueEmpty = syncRequestQueue.isEmpty();
        final boolean isNoStartedSync = startedSync.values().stream().noneMatch(SyncWrapper::isRunning);

        Timber.v("is queue empty ? %s ; is no started sync ? %s", isQueueEmpty, isNoStartedSync);
        final boolean isPausable = isQueueEmpty && isNoStartedSync;
        if (!isPausable) pauseStartTime = 0L;
        return isPausable;
    }

    /**
     * unpause synchronization
     */
    public void unPauseSync() {
        pauseStartTime = 0L;
    }

    private boolean isPaused() {
        if (pauseStartTime == 0L) return false;
        return System.currentTimeMillis() - pauseStartTime < maxPauseTimeInMs;
    }

    /**
     * Add a SyncRequest into waiting queue if it matches some conditions:
     * - an equivalent sync Request (same file & same operation type) isn't already running
     * - failure limit isn't reach for this file
     *
     * It also remove already existing request for the same file from the waiting queue
     * @param request request to add to waiting queue
     */
    public void queueSyncRequest(@NonNull SyncRequest request) {
        for (SyncWrapper syncWrapper : startedSync.values()) {
            //noinspection EqualsBetweenInconvertibleTypes
            if (syncWrapper.isRunning() && syncWrapper.equals(request)) {
                return;
            }
        }
        if (skipBecauseOfPreviousFail(request)) return;

        syncRequestQueue.remove(request);
        syncRequestQueue.add(request);
    }

    /**
     * Add a collection of SyncRequest into waiting queue if they met some conditions:
     * - an equivalent sync Request (same file & same operation type) isn't already running
     * - failure limit isn't reach for this file
     *
     * It also remove already existing request for the same file from the waiting queue
     * @param requests collections of requests to add
     */
    public void queueSyncRequests(@NonNull Collection<SyncRequest> requests) {
        for (SyncWrapper syncWrapper : startedSync.values()) {
            if (syncWrapper.isRunning()) {
                //noinspection EqualsBetweenInconvertibleTypes
                requests.removeIf(syncWrapper::equals);
            }
        }
        requests.removeIf(this::skipBecauseOfPreviousFail);

        syncRequestQueue.removeAll(requests);
        syncRequestQueue.addAll(requests);
    }

    /**
     * Progressively delay synchronization of a file in case of failure
     * @param request SyncRequest for the given file
     * @return false if file can be sync or true if it must wait
     */
    private boolean skipBecauseOfPreviousFail(SyncRequest request) {
        final FailedSyncPrefsManager failedSyncPref = FailedSyncPrefsManager.getInstance(getApplicationContext());
        final int fileStateId = request.getSyncedFileState().getId();
        final int failureCounter = failedSyncPref.getFailureCounterForFile(fileStateId);

        long delay;
        switch (failureCounter) {
            case 0:
                return false;
            case 1:
                delay = 3600; //1h
                break;
            case 2:
                delay = 7200; //2h
                break;
            case 3:
                delay = 18000; // 5h
                break;
            default:
                return true;
        }

        final long timeStamp = System.currentTimeMillis() / 1000;
        return timeStamp <  failedSyncPref.getLastFailureTimeForFile(fileStateId) + delay;
    }

    public void startSynchronization(){
        Timber.d("startAllThreads");
        for(int i =-1; ++i < workerAmount;){
@@ -236,18 +123,22 @@ public class SynchronizationService extends Service implements OnRemoteOperation

        if (!isNetworkAvailable()) {
            Timber.d("No network available: Clear syncRequestQueue");
            syncRequestQueue.clear();
            syncManager.clearRequestQueue();
            return;
        }

        if (!canStart(threadIndex) || isPaused()) {
            Timber.d("Can't start thread #%s, Sync is paused or thread is already running", threadIndex);
        if (!canStart(threadIndex)) {
            Timber.d("Can't start thread #%s, thread is already running", threadIndex);
            return;
        }

        final SyncRequest request = this.syncRequestQueue.poll(); //return null if empty
        final SyncRequest request = this.syncManager.pollSyncRequest(); //return null if empty
        if (request == null) {
            Timber.d("Thread #%s: No more sync request to start.", threadIndex);
            syncManager.removeStartedRequest(threadIndex);
            if (!syncManager.isAnySyncRequestRunning()) {
                syncManager.startListeningFiles(getApplication());
            }
            return;
        }

@@ -263,7 +154,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation
            final FileSyncDisabler fileSyncDisabler = new FileSyncDisabler(request.getSyncedFileState());
            threadPool[threadIndex] = new Thread(fileSyncDisabler.getRunnable(handler, threadIndex, getApplicationContext(), this));
            threadPool[threadIndex].start();
            startedSync.put(threadIndex, syncWrapper);
            syncManager.addStartedRequest(threadIndex,syncWrapper);
            return;
        }

@@ -278,7 +169,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation

        //noinspection deprecation
        threadPool[threadIndex] = operation.execute(ocClient, this, handler);
        startedSync.put(threadIndex, syncWrapper);
        syncManager.addStartedRequest(threadIndex, syncWrapper);
    }

    /**
@@ -287,7 +178,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation
     * @return false if nogo
     */
    private boolean canStart(int threadIndex) {
        final SyncWrapper syncWrapper = startedSync.get(threadIndex);
        final SyncWrapper syncWrapper = syncManager.getStartedRequestOnThread(threadIndex);
        return (syncWrapper == null || !syncWrapper.isRunning());
    }

@@ -318,7 +209,11 @@ public class SynchronizationService extends Service implements OnRemoteOperation
                break;
        }

        for (Map.Entry<Integer, SyncWrapper> keyValue : startedSync.entrySet()) {
        if (isNetworkDisconnected) {
            syncManager.clearRequestQueue();
        }

        for (Map.Entry<Integer, SyncWrapper> keyValue : syncManager.getStartedRequests().entrySet()) {
            final SyncWrapper wrapper = keyValue.getValue();
            final RemoteOperation wrapperOperation = wrapper.getRemoteOperation();
            if (wrapperOperation != null && wrapperOperation.equals(callerOperation)) {
@@ -328,10 +223,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation
                break;
            }
        }

        if (isNetworkDisconnected) {
            syncRequestQueue.clear();
        }
    }

    private void logSyncResult(@NonNull RemoteOperationResult.ResultCode resultCode, @NonNull RemoteOperation callerOperation) {
@@ -371,7 +262,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation

    @Override
    public void onSyncDisabled(int threadId, boolean succeed) {
        final SyncWrapper wrapper = startedSync.get(threadId);
        final SyncWrapper wrapper = syncManager.getStartedRequestOnThread(threadId);
        if (wrapper != null) {
            final SyncRequest request = wrapper.getRequest();
            Timber.d("%s sync disabled ? %s", request.getSyncedFileState().getLocalPath(), succeed);
@@ -381,10 +272,9 @@ public class SynchronizationService extends Service implements OnRemoteOperation
        startWorker(threadId);
    }

    public class SynchronizationBinder extends Binder {
        @NonNull
        public SynchronizationService getService(){
            return SynchronizationService.this;
        }
    @Nullable
    @Override
    public IBinder onBind(@Nullable Intent intent) {
        return null;
    }
}
 No newline at end of file
Loading