com.owncloud.android.files.services.AvailableOfflineSyncJobService.java Source code

Java tutorial

Introduction

Here is the source code for com.owncloud.android.files.services.AvailableOfflineSyncJobService.java

Source

/**
 * ownCloud Android client application
 *
 * @author David Gonzlez Verdugo
 * Copyright (C) 2018 ownCloud GmbH.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * This program 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 com.owncloud.android.files.services;

import android.accounts.Account;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.Pair;

import com.owncloud.android.BuildConfig;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AccountUtils;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.SynchronizeFileOperation;
import com.owncloud.android.ui.activity.ConflictsResolveActivity;
import com.owncloud.android.ui.notifications.NotificationUtils;
import com.owncloud.android.utils.Extras;
import com.owncloud.android.utils.FileStorageUtils;

import java.io.File;
import java.util.List;

/**
 * Job to watch for local changes in available offline files (formerly known as kept-in-sync files) and try to
 * synchronize them with the OC server.
 * This job should be executed every 15 minutes since a file is set as available offline for the first time and stopped
 * when there's no available offline files
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class AvailableOfflineSyncJobService extends JobService {
    private static final String TAG = "AvOfflineSyncJobService";
    private static final String FILE_SYNC_CONFLICT_CHANNEL_ID = "FILE_SYNC_CONFLICT_CHANNEL_ID";

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log_OC.d(TAG, "Starting job to sync available offline files");

        new AvailableOfflineJobTask(this).execute(jobParameters);

        return true; // True because we have a thread still running in background
    }

    private static class AvailableOfflineJobTask extends AsyncTask<JobParameters, Void, JobParameters> {

        private final JobService mAvailableOfflineJobService;

        public AvailableOfflineJobTask(JobService mAvailableOfflineJobService) {
            this.mAvailableOfflineJobService = mAvailableOfflineJobService;
        }

        @Override
        protected JobParameters doInBackground(JobParameters... jobParams) {

            String accountName = jobParams[0].getExtras().getString(Extras.EXTRA_ACCOUNT_NAME);

            Account account = AccountUtils.getOwnCloudAccountByName(mAvailableOfflineJobService, accountName);

            FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(mAvailableOfflineJobService,
                    account, mAvailableOfflineJobService.getContentResolver());

            List<Pair<OCFile, String>> availableOfflineFilesFromEveryAccount = fileDataStorageManager
                    .getAvailableOfflineFilesFromEveryAccount();

            // Cancel periodic job if there's no available offline files to watch for local changes
            if (availableOfflineFilesFromEveryAccount.isEmpty()) {
                cancelPeriodicJob(jobParams[0].getJobId());
                return jobParams[0];
            } else {
                syncAvailableOfflineFiles(availableOfflineFilesFromEveryAccount);
            }

            return jobParams[0];
        }

        private void syncAvailableOfflineFiles(List<Pair<OCFile, String>> availableOfflineFilesForAccount) {
            for (Pair<OCFile, String> fileForAccount : availableOfflineFilesForAccount) {

                String localPath = fileForAccount.first.getStoragePath();

                if (localPath == null) {
                    localPath = FileStorageUtils.getDefaultSavePathFor(fileForAccount.second, // Account name
                            fileForAccount.first // OCFile
                    );
                }

                File localFile = new File(localPath);

                if (localFile.lastModified() <= fileForAccount.first.getLastSyncDateForData()
                        && (BuildConfig.DEBUG || MainApp.isBeta())) {
                    Log_OC.i(TAG, "File " + fileForAccount.first.getRemotePath() + " already synchronized "
                            + "in account " + fileForAccount.second + ", ignoring");
                    continue;
                }

                startSyncOperation(fileForAccount.first, fileForAccount.second);
            }
        }

        /**
         * Triggers an operation to synchronize the contents of a recently modified available offline file with
         * its remote counterpart in the associated ownCloud account.
         * @param availableOfflineFile file to synchronize
         * @param accountName account to synchronize the available offline file with
         */
        private void startSyncOperation(OCFile availableOfflineFile, String accountName) {
            if (BuildConfig.DEBUG || MainApp.isBeta()) {
                Log_OC.i(TAG, String.format("Requested synchronization for file %1s in account %2s",
                        availableOfflineFile.getRemotePath(), accountName));
            }

            Account account = AccountUtils.getOwnCloudAccountByName(mAvailableOfflineJobService, accountName);

            FileDataStorageManager storageManager = new FileDataStorageManager(mAvailableOfflineJobService, account,
                    mAvailableOfflineJobService.getContentResolver());

            SynchronizeFileOperation synchronizeFileOperation = new SynchronizeFileOperation(availableOfflineFile,
                    null, account, false, mAvailableOfflineJobService, true);

            RemoteOperationResult result = synchronizeFileOperation.execute(storageManager,
                    mAvailableOfflineJobService);

            if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
                notifyConflict(availableOfflineFile, account);
            }
        }

        /**
         * Show a notification with file conflict information, that will open a dialog to solve it when tapping it
         * @param availableOfflineFile file in conflict
         * @param account account which the file in conflict belongs to
         */
        private void notifyConflict(OCFile availableOfflineFile, Account account) {
            NotificationManager notificationManager = (NotificationManager) mAvailableOfflineJobService
                    .getSystemService(NOTIFICATION_SERVICE);
            NotificationCompat.Builder notificationBuilder = NotificationUtils
                    .newNotificationBuilder(mAvailableOfflineJobService);

            // Configure notification channel
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                NotificationChannel notificationChannel;
                CharSequence name = mAvailableOfflineJobService
                        .getString(R.string.file_sync_notification_channel_name);
                String description = mAvailableOfflineJobService
                        .getString(R.string.file_sync_notification_channel_description);
                int importance = NotificationManager.IMPORTANCE_LOW;
                notificationChannel = new NotificationChannel(FILE_SYNC_CONFLICT_CHANNEL_ID, name, importance);
                notificationChannel.setDescription(description);
                notificationManager.createNotificationChannel(notificationChannel);
            }

            notificationBuilder.setChannelId(FILE_SYNC_CONFLICT_CHANNEL_ID)
                    .setSmallIcon(R.drawable.notification_icon)
                    .setTicker(mAvailableOfflineJobService.getString(R.string.conflict_title))
                    .setContentTitle(mAvailableOfflineJobService.getString(R.string.conflict_title))
                    .setContentText(
                            String.format(mAvailableOfflineJobService.getString(R.string.conflict_description),
                                    availableOfflineFile.getRemotePath()))
                    .setAutoCancel(true);

            Intent showConflictActivityIntent = new Intent(mAvailableOfflineJobService,
                    ConflictsResolveActivity.class);
            showConflictActivityIntent.setFlags(showConflictActivityIntent.getFlags()
                    | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND);
            showConflictActivityIntent.putExtra(ConflictsResolveActivity.EXTRA_FILE, availableOfflineFile);
            showConflictActivityIntent.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, account);

            notificationBuilder.setContentIntent(PendingIntent.getActivity(mAvailableOfflineJobService,
                    (int) System.currentTimeMillis(), showConflictActivityIntent, 0));

            int notificationId = 0;

            // We need a notification id for each file in conflict, let's use the file id but in a safe way
            if ((int) availableOfflineFile.getFileId() >= Integer.MIN_VALUE
                    && (int) availableOfflineFile.getFileId() <= Integer.MAX_VALUE) {
                notificationId = (int) availableOfflineFile.getFileId();
            }

            notificationManager.notify(notificationId, notificationBuilder.build());
        }

        /**
         * Cancel the periodic job
         * @param jobId id of the job to cancel
         */
        private void cancelPeriodicJob(int jobId) {
            JobScheduler jobScheduler = (JobScheduler) mAvailableOfflineJobService
                    .getSystemService(Context.JOB_SCHEDULER_SERVICE);

            jobScheduler.cancel(jobId);

            Log_OC.d(TAG, "No available offline files to check, cancelling the periodic job");
        }

        @Override
        protected void onPostExecute(JobParameters jobParameters) {
            mAvailableOfflineJobService.jobFinished(jobParameters, false);
        }
    }

    @Override
    /**
     * Called by the system if the job is cancelled before being finished
     */
    public boolean onStopJob(JobParameters jobParameters) {
        Log_OC.d(TAG, "Job " + TAG + " was cancelled before finishing.");
        return true;
    }
}