Migrate NContentObserverJob to WorkManager

Signed-off-by: Chris Narkiewicz <hello@ezaquarii.com>
This commit is contained in:
Chris Narkiewicz 2019-09-14 16:45:15 +01:00
parent 59e1f7fb88
commit 808c9098ea
No known key found for this signature in database
GPG key ID: 30D28CA4CCC665C6
16 changed files with 560 additions and 145 deletions

View file

@ -272,6 +272,8 @@ dependencies {
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
implementation "androidx.work:work-runtime:2.2.0"
implementation "androidx.work:work-runtime-ktx:2.2.0"
implementation 'com.github.albfernandez:juniversalchardet:2.0.3' // need this version for Android <7
implementation 'com.google.code.findbugs:annotations:2.0.1'
implementation 'commons-io:commons-io:2.6'
@ -315,6 +317,7 @@ dependencies {
ktlint "com.pinterest:ktlint:0.34.2"
implementation 'org.conscrypt:conscrypt-android:2.2.1'
// dependencies for local unit tests
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:3.1.0'

View file

@ -164,11 +164,6 @@
android:label="@string/app_name"
android:theme="@style/Theme.ownCloud.Fullscreen" />
<service
android:name=".jobs.NContentObserverJob"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<service
android:name=".authentication.AccountAuthenticatorService"
android:exported="true" >
@ -272,6 +267,14 @@
android:exported="true">
</provider>
<!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
<!-- to "best before" dates in his fridge. -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities=".workmanager-init"
android:exported="false"
tools:node="remove" />
<activity
android:name=".authentication.AuthenticatorActivity"
android:exported="true"

View file

@ -24,6 +24,7 @@ import android.app.Application;
import com.nextcloud.client.appinfo.AppInfoModule;
import com.nextcloud.client.device.DeviceModule;
import com.nextcloud.client.jobs.JobsModule;
import com.nextcloud.client.network.NetworkModule;
import com.nextcloud.client.onboarding.OnboardingModule;
import com.nextcloud.client.preferences.PreferencesModule;
@ -43,7 +44,8 @@ import dagger.android.support.AndroidSupportInjectionModule;
NetworkModule.class,
DeviceModule.class,
OnboardingModule.class,
ViewModelModule.class
ViewModelModule.class,
JobsModule.class
})
@Singleton
public interface AppComponent {

View file

@ -33,9 +33,9 @@ import com.nextcloud.client.account.CurrentAccountProvider;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.nextcloud.client.core.AsyncRunner;
import com.nextcloud.client.core.ThreadPoolAsyncRunner;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.core.ClockImpl;
import com.nextcloud.client.core.ThreadPoolAsyncRunner;
import com.nextcloud.client.device.DeviceInfo;
import com.nextcloud.client.logger.FileLogHandler;
import com.nextcloud.client.logger.Logger;
@ -71,6 +71,11 @@ class AppModule {
return application;
}
@Provides
ContentResolver contentResolver(Context context) {
return context.getContentResolver();
}
@Provides
Resources resources(Application application) {
return application.getResources();

View file

@ -0,0 +1,81 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.jobs
import android.content.ContentResolver
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.work.ListenableWorker
import androidx.work.WorkerFactory
import androidx.work.WorkerParameters
import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.preferences.AppPreferences
import com.owncloud.android.datamodel.SyncedFolderProvider
import javax.inject.Inject
import javax.inject.Provider
/**
* This factory is responsible for creating all background jobs and for injecting
* all jobs dependencies.
*/
class BackgroundJobFactory @Inject constructor(
private val preferences: AppPreferences,
private val contentResolver: ContentResolver,
private val powerManagerService: PowerManagementService,
private val backgroundJobManager: Provider<BackgroundJobManager>,
private val deviceInfo: DeviceInfo
) : WorkerFactory() {
override fun createWorker(
context: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
val workerClass = try {
Class.forName(workerClassName).kotlin
} catch (ex: ClassNotFoundException) {
null
}
return when (workerClass) {
ContentObserverWork::class -> createContentObserverJob(context, workerParameters)
else -> null // falls back to default factory
}
}
private fun createContentObserverJob(context: Context, workerParameters: WorkerParameters): ListenableWorker? {
val folderResolver = SyncedFolderProvider(contentResolver, preferences)
@RequiresApi(Build.VERSION_CODES.N)
if (deviceInfo.apiLevel >= Build.VERSION_CODES.N) {
return ContentObserverWork(
context,
workerParameters,
folderResolver,
powerManagerService,
backgroundJobManager.get()
)
} else {
return null
}
}
}

View file

@ -0,0 +1,37 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.jobs
import android.os.Build
import androidx.annotation.RequiresApi
/**
* This interface allows to control, schedule and monitor all application
* long-running background tasks, such as periodic checks or synchronization.
*/
interface BackgroundJobManager {
/**
* Start content observer job that monitors changes in media folders
* and launches synchronization when needed.
*/
@RequiresApi(Build.VERSION_CODES.N)
fun scheduleContentObserverJob()
}

View file

@ -0,0 +1,54 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.jobs
import android.os.Build
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import androidx.work.Constraints
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
internal class BackgroundJobManagerImpl(private val workManager: WorkManager) : BackgroundJobManager {
companion object {
const val TAG_CONTENT_SYNC = "content_sync"
const val MAX_CONTENT_TRIGGER_DELAY_MS = 1500L
}
@RequiresApi(Build.VERSION_CODES.N)
override fun scheduleContentObserverJob() {
val constrains = Constraints.Builder()
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
.setTriggerContentMaxDelay(MAX_CONTENT_TRIGGER_DELAY_MS, TimeUnit.MILLISECONDS)
.build()
val request = OneTimeWorkRequest.Builder(ContentObserverWork::class.java)
.setConstraints(constrains)
.addTag(TAG_CONTENT_SYNC)
.build()
workManager.enqueue(request)
}
}

View file

@ -0,0 +1,85 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.jobs
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.evernote.android.job.JobRequest
import com.evernote.android.job.util.support.PersistableBundleCompat
import com.nextcloud.client.device.PowerManagementService
import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.jobs.FilesSyncJob
import com.owncloud.android.jobs.MediaFoldersDetectionJob
/**
* This work is triggered when OS detects change in media folders.
*
* It fires media detection job and sync job and finishes immediately.
*
* This job must not be started on API < 24.
*/
@RequiresApi(Build.VERSION_CODES.N)
class ContentObserverWork(
appContext: Context,
private val params: WorkerParameters,
private val syncerFolderProvider: SyncedFolderProvider,
private val powerManagementService: PowerManagementService,
private val backgroundJobManager: BackgroundJobManager
) : Worker(appContext, params) {
override fun doWork(): Result {
if (params.triggeredContentUris.size > 0) {
checkAndStartFileSyncJob()
startMediaFolderDetectionJob()
}
recheduleSelf()
return Result.success()
}
private fun recheduleSelf() {
backgroundJobManager.scheduleContentObserverJob()
}
private fun checkAndStartFileSyncJob() {
val syncFolders = syncerFolderProvider.countEnabledSyncedFolders() > 0
if (!powerManagementService.isPowerSavingEnabled && syncFolders) {
val persistableBundleCompat = PersistableBundleCompat()
persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true)
JobRequest.Builder(FilesSyncJob.TAG)
.startNow()
.setExtras(persistableBundleCompat)
.setUpdateCurrent(false)
.build()
.schedule()
}
}
private fun startMediaFolderDetectionJob() {
JobRequest.Builder(MediaFoldersDetectionJob.TAG)
.startNow()
.setUpdateCurrent(false)
.build()
.schedule()
}
}

View file

@ -0,0 +1,50 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.jobs
import android.content.Context
import android.content.ContextWrapper
import androidx.work.Configuration
import androidx.work.WorkManager
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class JobsModule {
@Provides
@Singleton
fun backgroundJobManager(context: Context, factory: BackgroundJobFactory): BackgroundJobManager {
val configuration = Configuration.Builder()
.setWorkerFactory(factory)
.build()
val contextWrapper = object : ContextWrapper(context) {
override fun getApplicationContext(): Context {
return this
}
}
WorkManager.initialize(contextWrapper, configuration)
val wm = WorkManager.getInstance(context)
return BackgroundJobManagerImpl(wm)
}
}

View file

@ -49,6 +49,7 @@ import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.di.ActivityInjector;
import com.nextcloud.client.di.DaggerAppComponent;
import com.nextcloud.client.errorhandling.ExceptionHandler;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.logger.LegacyLoggerAdapter;
import com.nextcloud.client.logger.Logger;
import com.nextcloud.client.network.ConnectivityService;
@ -156,6 +157,9 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
@Inject
AppInfo appInfo;
@Inject
BackgroundJobManager backgroundJobManager;
private PassCodeManager passCodeManager;
@SuppressWarnings("unused")
@ -184,6 +188,14 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
return powerManagementService;
}
/**
* Temporary getter enabling intermediate refactoring.
* TODO: remove when FileSyncHelper is refactored/removed
*/
public BackgroundJobManager getBackgroundJobManager() {
return backgroundJobManager;
}
private String getAppProcessName() {
String processName = "";
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
@ -286,8 +298,11 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
Log_OC.d("Debug", "Failed to disable uri exposure");
}
}
initSyncOperations(uploadsStorageManager, accountManager, connectivityService, powerManagementService);
initSyncOperations(uploadsStorageManager,
accountManager,
connectivityService,
powerManagementService,
backgroundJobManager);
initContactsBackup(accountManager);
notificationChannels();
@ -444,7 +459,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
final UploadsStorageManager uploadsStorageManager,
final UserAccountManager accountManager,
final ConnectivityService connectivityService,
final PowerManagementService powerManagementService
final PowerManagementService powerManagementService,
final BackgroundJobManager jobManager
) {
updateToAutoUpload();
cleanOldEntries();
@ -462,7 +478,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
initiateExistingAutoUploadEntries();
FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext);
FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, jobManager);
FilesSyncHelper.restartJobsIfNeeded(
uploadsStorageManager,
accountManager,

View file

@ -29,6 +29,7 @@ import android.content.Intent;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.UploadsStorageManager;
@ -51,6 +52,7 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
@Inject UploadsStorageManager uploadsStorageManager;
@Inject ConnectivityService connectivityService;
@Inject PowerManagementService powerManagementService;
@Inject BackgroundJobManager backgroundJobManager;
/**
* Receives broadcast intent reporting that the system was just boot up.
@ -66,7 +68,8 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
MainApp.initSyncOperations(uploadsStorageManager,
accountManager,
connectivityService,
powerManagementService);
powerManagementService,
backgroundJobManager);
MainApp.initContactsBackup(accountManager);
} else {
Log_OC.d(TAG, "Getting wrong intent: " + intent.getAction());

View file

@ -74,7 +74,7 @@ import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
*/
public class FilesSyncJob extends Job {
public static final String TAG = "FilesSyncJob";
static final String SKIP_CUSTOM = "skipCustom";
public static final String SKIP_CUSTOM = "skipCustom";
public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving";
private static final String WAKELOCK_TAG_SEPARATION = ":";

View file

@ -1,102 +0,0 @@
/*
* Nextcloud Android client application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2017 Nextcloud
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.jobs;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.os.Build;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.utils.FilesSyncHelper;
import androidx.annotation.RequiresApi;
/*
Job that triggers new FilesSyncJob in case new photo or video were detected
and starts a job to find new media folders
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class NContentObserverJob extends JobService {
private PowerManagementService powerManagementService;
private AppPreferences preferences;
@Override
public void onCreate() {
super.onCreate();
// Temporary workaround for https://github.com/nextcloud/android/issues/4147
// TODO: this must be fixed properly
MainApp app = (MainApp) getApplication();
powerManagementService = app.getPowerManagementService();
preferences = app.getPreferences();
}
@Override
public boolean onStartJob(JobParameters params) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (params.getJobId() == FilesSyncHelper.ContentSyncJobId && params.getTriggeredContentAuthorities()
!= null && params.getTriggeredContentUris() != null
&& params.getTriggeredContentUris().length > 0) {
checkAndStartFileSyncJob();
new JobRequest.Builder(MediaFoldersDetectionJob.TAG)
.startNow()
.setUpdateCurrent(false)
.build()
.schedule();
}
FilesSyncHelper.scheduleJobOnN();
}
return true;
}
private void checkAndStartFileSyncJob() {
if (!powerManagementService.isPowerSavingEnabled() &&
new SyncedFolderProvider(getContentResolver(), preferences).countEnabledSyncedFolders() > 0) {
PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat();
persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true);
new JobRequest.Builder(FilesSyncJob.TAG)
.startNow()
.setExtras(persistableBundleCompat)
.setUpdateCurrent(false)
.build()
.schedule();
}
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}

View file

@ -24,9 +24,6 @@
package com.owncloud.android.utils;
import android.accounts.Account;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
@ -40,6 +37,7 @@ import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.MainApp;
@ -52,7 +50,6 @@ import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.jobs.FilesSyncJob;
import com.owncloud.android.jobs.NContentObserverJob;
import com.owncloud.android.jobs.OfflineSyncJob;
import org.lukhnos.nnio.file.FileVisitResult;
@ -259,7 +256,7 @@ public final class FilesSyncHelper {
}).start();
}
public static void scheduleFilesSyncIfNeeded(Context context) {
public static void scheduleFilesSyncIfNeeded(Context context, BackgroundJobManager jobManager) {
// always run this because it also allows us to perform retries of manual uploads
new JobRequest.Builder(FilesSyncJob.TAG)
.setPeriodic(900000L, 300000L)
@ -268,7 +265,7 @@ public final class FilesSyncHelper {
.schedule();
if (context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
scheduleJobOnN();
jobManager.scheduleContentObserverJob();
}
}
@ -282,29 +279,5 @@ public final class FilesSyncHelper {
.schedule();
}
}
@RequiresApi(api = Build.VERSION_CODES.N)
public static void scheduleJobOnN() {
JobScheduler jobScheduler = MainApp.getAppContext().getSystemService(JobScheduler.class);
if (jobScheduler != null) {
JobInfo.Builder builder = new JobInfo.Builder(ContentSyncJobId, new ComponentName(MainApp.getAppContext(),
NContentObserverJob.class.getName()));
builder.addTriggerContentUri(new JobInfo.TriggerContentUri(android.provider.MediaStore.
Images.Media.INTERNAL_CONTENT_URI,
JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MediaStore.
Images.Media.EXTERNAL_CONTENT_URI,
JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
builder.addTriggerContentUri(new JobInfo.TriggerContentUri(android.provider.MediaStore.
Video.Media.INTERNAL_CONTENT_URI,
JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MediaStore.
Video.Media.EXTERNAL_CONTENT_URI,
JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
builder.setTriggerContentMaxDelay(1500);
jobScheduler.schedule(builder.build());
}
}
}

View file

@ -0,0 +1,87 @@
package com.nextcloud.client.jobs
import android.content.ContentResolver
import android.content.Context
import android.os.Build
import androidx.work.WorkerParameters
import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.preferences.AppPreferences
import com.nhaarman.mockitokotlin2.whenever
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import javax.inject.Provider
class BackgroundJobFactoryTest {
@Mock
private lateinit var context: Context
@Mock
private lateinit var params: WorkerParameters
@Mock
private lateinit var contentResolver: ContentResolver
@Mock
private lateinit var preferences: AppPreferences
@Mock
private lateinit var powerManagementService: PowerManagementService
@Mock
private lateinit var backgroundJobManager: BackgroundJobManager
@Mock
private lateinit var deviceInfo: DeviceInfo
private lateinit var factory: BackgroundJobFactory
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
factory = BackgroundJobFactory(
preferences,
contentResolver,
powerManagementService,
Provider { backgroundJobManager },
deviceInfo
)
}
@Test
fun `worker is created on api level 24+`() {
// GIVEN
// api level is > 24
// content URI trigger is supported
whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.N)
// WHEN
// factory is called to create content observer worker
val worker = factory.createWorker(context, ContentObserverWork::class.java.name, params)
// THEN
// factory creates a worker compatible with API level
assertNotNull(worker)
}
@Test
fun `worker is not created below api level 24`() {
// GIVEN
// api level is < 24
// content URI trigger is not supported
whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.M)
// WHEN
// factory is called to create content observer worker
val worker = factory.createWorker(context, ContentObserverWork::class.java.name, params)
// THEN
// factory does not create a worker incompatible with API level
assertNull(worker)
}
}

View file

@ -0,0 +1,118 @@
package com.nextcloud.client.jobs
import android.content.Context
import android.net.Uri
import androidx.work.WorkerParameters
import com.nextcloud.client.device.PowerManagementService
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import com.owncloud.android.datamodel.SyncedFolderProvider
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
class ContentObserverWorkTest {
private lateinit var worker: ContentObserverWork
@Mock
lateinit var params: WorkerParameters
@Mock
lateinit var context: Context
@Mock
lateinit var folderProvider: SyncedFolderProvider
@Mock
lateinit var powerManagementService: PowerManagementService
@Mock
lateinit var backgroundJobManager: BackgroundJobManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
worker = ContentObserverWork(
appContext = context,
params = params,
syncerFolderProvider = folderProvider,
powerManagementService = powerManagementService,
backgroundJobManager = backgroundJobManager
)
val uri: Uri = Mockito.mock(Uri::class.java)
whenever(params.triggeredContentUris).thenReturn(listOf(uri))
}
@Test
fun `job reschedules self after each run unconditionally`() {
// GIVEN
// nothing to sync
whenever(params.triggeredContentUris).thenReturn(emptyList())
// WHEN
// worker is called
worker.doWork()
// THEN
// worker reschedules itself unconditionally
verify(backgroundJobManager).scheduleContentObserverJob()
}
@Test
@Ignore("TODO: needs further refactoring")
fun `sync is triggered`() {
// GIVEN
// power saving is disabled
// some folders are configured for syncing
whenever(powerManagementService.isPowerSavingEnabled).thenReturn(false)
whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(1)
// WHEN
// worker is called
worker.doWork()
// THEN
// sync job is scheduled
// TO DO: verify(backgroundJobManager).sheduleFilesSync() or something like this
}
@Test
@Ignore("TODO: needs further refactoring")
fun `sync is not triggered under power saving mode`() {
// GIVEN
// power saving is enabled
// some folders are configured for syncing
whenever(powerManagementService.isPowerSavingEnabled).thenReturn(true)
whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(1)
// WHEN
// worker is called
worker.doWork()
// THEN
// sync job is scheduled
// TO DO: verify(backgroundJobManager, never()).sheduleFilesSync() or something like this)
}
@Test
@Ignore("TODO: needs further refactoring")
fun `sync is not triggered if no folder are synced`() {
// GIVEN
// power saving is disabled
// no folders configured for syncing
whenever(powerManagementService.isPowerSavingEnabled).thenReturn(false)
whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(0)
// WHEN
// worker is called
worker.doWork()
// THEN
// sync job is scheduled
// TO DO: verify(backgroundJobManager, never()).sheduleFilesSync() or something like this)
}
}