mirror of
https://github.com/nextcloud/android.git
synced 2024-11-26 07:05:49 +03:00
Migrate NContentObserverJob to WorkManager
Signed-off-by: Chris Narkiewicz <hello@ezaquarii.com>
This commit is contained in:
parent
59e1f7fb88
commit
808c9098ea
16 changed files with 560 additions and 145 deletions
|
@ -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'
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
50
src/main/java/com/nextcloud/client/jobs/JobsModule.kt
Normal file
50
src/main/java/com/nextcloud/client/jobs/JobsModule.kt
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 = ":";
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue