├── .gitignore ├── LICENSE ├── README.MD ├── build.gradle ├── foreground ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── sdex │ │ └── workmanager │ │ ├── ForegroundActivity.kt │ │ └── ForegroundWorker.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── baseline_build_24.xml │ └── ic_launcher_background.xml │ ├── layout │ └── activity_foreground.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── uri_trigger ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── java └── dev │ └── sdex │ └── uritrigger │ ├── UriTriggerActivity.kt │ └── UriTriggerWorker.kt └── res ├── drawable-v24 └── ic_launcher_foreground.xml ├── drawable └── ic_launcher_background.xml ├── layout └── activity_uri_trigger.xml ├── mipmap-anydpi-v26 ├── ic_launcher.xml └── ic_launcher_round.xml ├── mipmap-hdpi ├── ic_launcher.webp └── ic_launcher_round.webp ├── mipmap-mdpi ├── ic_launcher.webp └── ic_launcher_round.webp ├── mipmap-xhdpi ├── ic_launcher.webp └── ic_launcher_round.webp ├── mipmap-xxhdpi ├── ic_launcher.webp └── ic_launcher_round.webp ├── mipmap-xxxhdpi ├── ic_launcher.webp └── ic_launcher_round.webp └── values ├── colors.xml ├── strings.xml └── themes.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | 11 | # built application files 12 | *.apk 13 | *.ap_ 14 | 15 | # files for the dex VM 16 | *.dex 17 | 18 | # Java class files 19 | *.class 20 | 21 | # generated files 22 | bin/ 23 | gen/ 24 | out/ 25 | build/ 26 | .google_apis/ 27 | .settings/ 28 | 29 | # Local configuration file (sdk path, etc) 30 | local.properties 31 | project.properties 32 | keystore.properties 33 | 34 | # Eclipse project files 35 | .classpath 36 | .project 37 | 38 | # Windows thumbnail db 39 | .DS_Store 40 | 41 | # IDEA/Android Studio project files, because 42 | # the project can be imported from settings.gradle 43 | .idea 44 | *.iml 45 | 46 | # Old-style IDEA project files 47 | *.ipr 48 | *.iws 49 | 50 | # Local IDEA workspace 51 | .idea/workspace.xml 52 | 53 | .navigation 54 | 55 | # Gradle cache 56 | .gradle 57 | 58 | # Sandbox stuff 59 | _sandbox 60 | 61 | # Crashlytics 62 | app/src/main/assets/crashlytics-build.properties 63 | 64 | # release builds 65 | release 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yuriy Mysochenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ## The collection of WorkManager samples 2 | 3 | ### foreground 4 | Shows how to run a WorkManager's Worker in foreground service and display progress in the notification bar. 5 | More in the post: https://medium.com/@sdex/run-worker-as-foreground-service-b607819fd263 6 | 7 | ### uri_trigger 8 | Use WorkManager to detect new media files -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.9.22' 3 | ext.appcompat = '1.6.1' 4 | ext.core = '1.12.0' 5 | ext.work_manager = '2.9.0' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:8.2.2' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /foreground/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /foreground/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.sdex.workmanager' 8 | compileSdk 34 9 | 10 | defaultConfig { 11 | applicationId "com.sdex.workmanager" 12 | minSdk 24 13 | targetSdk 34 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_17 29 | targetCompatibility JavaVersion.VERSION_17 30 | } 31 | kotlinOptions { 32 | jvmTarget = '17' 33 | } 34 | viewBinding { 35 | enabled = true 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation "androidx.appcompat:appcompat:$appcompat" 41 | implementation "androidx.core:core-ktx:$core" 42 | implementation "androidx.work:work-runtime-ktx:$work_manager" 43 | } 44 | -------------------------------------------------------------------------------- /foreground/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /foreground/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /foreground/src/main/java/com/sdex/workmanager/ForegroundActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sdex.workmanager 2 | 3 | import android.Manifest 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import android.os.Bundle 7 | import androidx.activity.result.contract.ActivityResultContracts 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.core.content.ContextCompat 10 | import androidx.work.OneTimeWorkRequestBuilder 11 | import androidx.work.WorkInfo 12 | import androidx.work.WorkManager 13 | import com.sdex.workmanager.ForegroundWorker.Companion.ARG_PROGRESS 14 | import com.sdex.workmanager.databinding.ActivityForegroundBinding 15 | 16 | class ForegroundActivity : AppCompatActivity() { 17 | 18 | private lateinit var binding: ActivityForegroundBinding 19 | private val requestPermissionLauncher = 20 | registerForActivityResult( 21 | ActivityResultContracts.RequestPermission() 22 | ) { 23 | // we don't care about the result, start the job in any case 24 | startJob() 25 | } 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | binding = ActivityForegroundBinding.inflate(layoutInflater) 30 | setContentView(binding.root) 31 | 32 | binding.start.setOnClickListener { 33 | // ask for permission to show notifications on android 13+ 34 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU 35 | || ContextCompat.checkSelfPermission( 36 | this, 37 | Manifest.permission.POST_NOTIFICATIONS 38 | ) == PackageManager.PERMISSION_GRANTED 39 | ) { 40 | startJob() 41 | } else { 42 | requestPermissionLauncher.launch( 43 | Manifest.permission.POST_NOTIFICATIONS 44 | ) 45 | } 46 | } 47 | } 48 | 49 | private fun startJob() { 50 | val workManager = WorkManager.getInstance(this) 51 | val workRequest = OneTimeWorkRequestBuilder().build() 52 | // observe the job progress in the activity 53 | workManager.getWorkInfoByIdLiveData(workRequest.id) 54 | .observe(this) { workInfo: WorkInfo? -> 55 | if (workInfo != null) { 56 | val progress = workInfo.progress 57 | val value = progress.getInt(ARG_PROGRESS, 0) 58 | binding.progressBar.progress = value 59 | 60 | if (workInfo.state == WorkInfo.State.SUCCEEDED) { 61 | binding.start.isEnabled = true 62 | } 63 | } 64 | } 65 | // run the worker 66 | workManager.enqueue(workRequest) 67 | 68 | binding.start.isEnabled = false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /foreground/src/main/java/com/sdex/workmanager/ForegroundWorker.kt: -------------------------------------------------------------------------------- 1 | package com.sdex.workmanager 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.content.Context 6 | import android.content.pm.ServiceInfo 7 | import android.os.Build 8 | import android.util.Log 9 | import androidx.core.app.NotificationCompat 10 | import androidx.work.CoroutineWorker 11 | import androidx.work.ForegroundInfo 12 | import androidx.work.WorkerParameters 13 | import androidx.work.workDataOf 14 | import kotlinx.coroutines.delay 15 | 16 | class ForegroundWorker( 17 | appContext: Context, 18 | params: WorkerParameters 19 | ) : CoroutineWorker(appContext, params) { 20 | 21 | private val notificationManager = appContext.getSystemService(NotificationManager::class.java) 22 | 23 | private val notificationBuilder = NotificationCompat.Builder(applicationContext, CHANNEL_ID) 24 | .setSmallIcon(R.drawable.baseline_build_24) 25 | .setContentTitle("Important background job") 26 | 27 | override suspend fun doWork(): Result { 28 | Log.d(TAG, "Start job") 29 | 30 | createNotificationChannel() 31 | val notification = notificationBuilder.build() 32 | val foregroundInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 33 | ForegroundInfo(NOTIFICATION_ID, notification, 34 | ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC 35 | ) 36 | } else { 37 | ForegroundInfo(NOTIFICATION_ID, notification) 38 | } 39 | setForegroundAsync(foregroundInfo) 40 | 41 | for (i in 0..100) { 42 | // we need it to get progress in UI 43 | setProgress(workDataOf(ARG_PROGRESS to i)) 44 | // update the notification progress 45 | showProgress(i) 46 | delay(DELAY_DURATION) 47 | } 48 | 49 | Log.d(TAG, "Finish job") 50 | return Result.success() 51 | } 52 | 53 | private suspend fun showProgress(progress: Int) { 54 | val notification = notificationBuilder 55 | .setProgress(100, progress, false) 56 | .build() 57 | val foregroundInfo = ForegroundInfo(NOTIFICATION_ID, notification) 58 | setForeground(foregroundInfo) 59 | } 60 | 61 | private fun createNotificationChannel() { 62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 63 | val notificationChannel = notificationManager?.getNotificationChannel(CHANNEL_ID) 64 | if (notificationChannel == null) { 65 | notificationManager?.createNotificationChannel( 66 | NotificationChannel( 67 | CHANNEL_ID, TAG, NotificationManager.IMPORTANCE_LOW 68 | ) 69 | ) 70 | } 71 | } 72 | } 73 | 74 | companion object { 75 | 76 | const val TAG = "ForegroundWorker" 77 | const val NOTIFICATION_ID = 42 78 | const val CHANNEL_ID = "Job progress" 79 | const val ARG_PROGRESS = "Progress" 80 | private const val DELAY_DURATION = 100L // ms 81 | } 82 | } -------------------------------------------------------------------------------- /foreground/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /foreground/src/main/res/drawable/baseline_build_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /foreground/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /foreground/src/main/res/layout/activity_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 |