diff --git a/backgroundwork/build.gradle.kts b/backgroundwork/build.gradle.kts index a7da5e6f3..0f212b4ea 100644 --- a/backgroundwork/build.gradle.kts +++ b/backgroundwork/build.gradle.kts @@ -28,4 +28,10 @@ android { dependencies { implementation(libs.androidx.core.ktx) implementation(libs.appcompat) + implementation(libs.androidx.work.runtime) + implementation(libs.androidx.work.runtime.ktx) + implementation(libs.androidx.work.multiprocess) + implementation(libs.androidx.concurrent.futures.ktx) + implementation(libs.google.android.material) + implementation(libs.guava.android) } diff --git a/backgroundwork/src/main/AndroidManifest.xml b/backgroundwork/src/main/AndroidManifest.xml index 7b483aecc..01d4830e1 100644 --- a/backgroundwork/src/main/AndroidManifest.xml +++ b/backgroundwork/src/main/AndroidManifest.xml @@ -18,7 +18,28 @@ xmlns:tools="http://schemas.android.com/tools"> + + + + + - + + + + + + + diff --git a/backgroundwork/src/main/java/com/example/snippets/backgroundwork/CoroutineWorkerThreading.kt b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/CoroutineWorkerThreading.kt new file mode 100644 index 000000000..f86c8f31b --- /dev/null +++ b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/CoroutineWorkerThreading.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.backgroundwork + +import android.content.ComponentName +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkerParameters +import androidx.work.multiprocess.RemoteCoroutineWorker +import androidx.work.multiprocess.RemoteListenableWorker.ARGUMENT_CLASS_NAME +import androidx.work.multiprocess.RemoteListenableWorker.ARGUMENT_PACKAGE_NAME +import androidx.work.multiprocess.RemoteWorkerService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +private object SimpleCoroutineDownloadWorker { + // [START android_background_coroutine_download_worker] + class CoroutineDownloadWorker( + context: Context, + params: WorkerParameters + ) : CoroutineWorker(context, params) { + + override suspend fun doWork(): Result { + val data = downloadSynchronously("https://www.google.com") + saveData(data) + return Result.success() + } + } + // [END android_background_coroutine_download_worker] +} + +private object WithContextCoroutineDownloadWorker { + // [START android_background_coroutine_download_worker_with_context] + class CoroutineDownloadWorker( + context: Context, + params: WorkerParameters + ) : CoroutineWorker(context, params) { + + override suspend fun doWork(): Result { + return withContext(Dispatchers.IO) { + val data = downloadSynchronously("https://www.google.com") + saveData(data) + Result.success() + } + } + } + // [END android_background_coroutine_download_worker_with_context] +} + +private fun remoteCoroutineWorkerRequest(): OneTimeWorkRequest { + // [START android_background_remote_coroutine_worker_request] + val PACKAGE_NAME = "com.example.background.multiprocess" + + val serviceName = RemoteWorkerService::class.java.name + val componentName = ComponentName(PACKAGE_NAME, serviceName) + + val data: Data = Data.Builder() + .putString(ARGUMENT_PACKAGE_NAME, componentName.packageName) + .putString(ARGUMENT_CLASS_NAME, componentName.className) + .build() + + return OneTimeWorkRequest.Builder(ExampleRemoteCoroutineWorker::class.java) + .setInputData(data) + .build() + // [END android_background_remote_coroutine_worker_request] +} + +private fun downloadSynchronously(url: String): String = "" +private fun saveData(data: String) {} + +class ExampleRemoteCoroutineWorker( + context: Context, + parameters: WorkerParameters +) : RemoteCoroutineWorker(context, parameters) { + override suspend fun doRemoteWork(): Result = Result.success() +} diff --git a/backgroundwork/src/main/java/com/example/snippets/backgroundwork/CustomConfiguration.kt b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/CustomConfiguration.kt new file mode 100644 index 000000000..ff9d0d896 --- /dev/null +++ b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/CustomConfiguration.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.backgroundwork + +import android.app.Application +import android.content.Context +import androidx.work.Configuration +import androidx.work.WorkManager + +// [START android_background_custom_configuration_on_demand] +class MyApplication() : Application(), Configuration.Provider { + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setMinimumLoggingLevel(android.util.Log.INFO) + .build() +} +// [END android_background_custom_configuration_on_demand] + +private fun Context.manualInitialization() { + // [START android_background_custom_configuration_manual_init] + // provide custom configuration. + val myConfig = Configuration.Builder() + .setMinimumLoggingLevel(android.util.Log.INFO) + .build() + + // initialize WorkManager. + WorkManager.initialize(this, myConfig) + // [END android_background_custom_configuration_manual_init] +} diff --git a/backgroundwork/src/main/java/com/example/snippets/backgroundwork/DefineWork.kt b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/DefineWork.kt new file mode 100644 index 000000000..f8564cfc7 --- /dev/null +++ b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/DefineWork.kt @@ -0,0 +1,194 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.backgroundwork + +import android.app.Notification +import android.content.Context +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.ForegroundInfo +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequest +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.OutOfQuotaPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkRequest +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import java.util.concurrent.TimeUnit + +private fun enqueueWorkSample(myContext: Context) { + // [START android_background_enqueue_work_request] + val myWorkRequest = OneTimeWorkRequestBuilder().build() + WorkManager.getInstance(myContext).enqueue(myWorkRequest) + // [END android_background_enqueue_work_request] +} + +private fun scheduleOneTimeWorkFrom() { + // [START android_background_schedule_one_time_work_from] + val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java) + // [END android_background_schedule_one_time_work_from] +} + +private fun scheduleOneTimeWorkBuilder() { + // [START android_background_schedule_one_time_work_builder] + val uploadWorkRequest: WorkRequest = + OneTimeWorkRequestBuilder() + // Additional configuration. + .build() + // [END android_background_schedule_one_time_work_builder] +} + +private fun expeditedWorkRequest(context: Context) { + // [START android_background_expedited_work_request] + val request = OneTimeWorkRequestBuilder() + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .build() + + WorkManager.getInstance(context) + .enqueue(request) + // [END android_background_expedited_work_request] +} + +// [START android_background_expedited_coroutine_worker] +class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { + + override suspend fun getForegroundInfo(): ForegroundInfo { + return ForegroundInfo( + NOTIFICATION_ID, createNotification() + ) + } + + override suspend fun doWork(): Result { + TODO() + } + + private fun createNotification(): Notification { + TODO() + } + + // [START_EXCLUDE silent] + companion object { + private const val NOTIFICATION_ID = 1 + } + // [END_EXCLUDE] +} +// [END android_background_expedited_coroutine_worker] + +private fun schedulePeriodicWork() { + // [START android_background_periodic_work_request] + val saveRequest = + PeriodicWorkRequestBuilder(1, TimeUnit.HOURS) + // Additional configuration. + .build() + // [END android_background_periodic_work_request] +} + +private fun schedulePeriodicWorkFlex() { + // [START android_background_periodic_work_request_flex] + val myUploadWork = PeriodicWorkRequestBuilder( + 1, TimeUnit.HOURS, // repeatInterval (the period cycle). + 15, TimeUnit.MINUTES + ) // flexInterval. + .build() + // [END android_background_periodic_work_request_flex] +} + +private fun workConstraints() { + // [START android_background_work_constraints] + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresCharging(true) + .build() + + val myWorkRequest: WorkRequest = + OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .build() + // [END android_background_work_constraints] +} + +private fun delayedWork() { + // [START android_background_delayed_work] + val myWorkRequest = OneTimeWorkRequestBuilder() + .setInitialDelay(10, TimeUnit.MINUTES) + .build() + // [END android_background_delayed_work] +} + +private fun retryBackoffPolicy() { + // [START android_background_retry_backoff_policy] + val myWorkRequest = OneTimeWorkRequestBuilder() + .setBackoffCriteria( + BackoffPolicy.LINEAR, + WorkRequest.MIN_BACKOFF_MILLIS, + TimeUnit.MILLISECONDS + ) + .build() + // [END android_background_retry_backoff_policy] +} + +private fun tagWork() { + // [START android_background_tag_work] + val myWorkRequest = OneTimeWorkRequestBuilder() + .addTag("cleanup") + .build() + // [END android_background_tag_work] +} + +// [START android_background_assign_input_data] +// Define the Worker requiring input. +class UploadWork(appContext: Context, workerParams: WorkerParameters) : + Worker(appContext, workerParams) { + + override fun doWork(): Result { + val imageUriInput = + inputData.getString("IMAGE_URI") ?: return Result.failure() + + uploadFile(imageUriInput) + return Result.success() + } + // [START_EXCLUDE] + private fun uploadFile(uri: String) {} + // [END_EXCLUDE] +} + +// Create a WorkRequest for your Worker and sending it input. +val myUploadWork = OneTimeWorkRequestBuilder() + .setInputData( + workDataOf( + "IMAGE_URI" to "http://..." + ) + ) + .build() +// [END android_background_assign_input_data] + +class MyWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + override fun doWork(): Result = Result.success() +} + +class SyncWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + override fun doWork(): Result = Result.success() +} + +class SaveImageToFileWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + override fun doWork(): Result = Result.success() +} diff --git a/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ListenableWorkerThreading.kt b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ListenableWorkerThreading.kt new file mode 100644 index 000000000..a3202b62c --- /dev/null +++ b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ListenableWorkerThreading.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.backgroundwork + +import android.content.ComponentName +import android.content.Context +import androidx.concurrent.futures.CallbackToFutureAdapter +import androidx.work.Data +import androidx.work.ListenableWorker +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkerParameters +import androidx.work.multiprocess.RemoteListenableWorker +import androidx.work.multiprocess.RemoteListenableWorker.ARGUMENT_CLASS_NAME +import androidx.work.multiprocess.RemoteListenableWorker.ARGUMENT_PACKAGE_NAME +import androidx.work.multiprocess.RemoteWorkerService +import com.google.common.util.concurrent.ListenableFuture +import java.io.IOException +import java.util.concurrent.Executor +import java.util.concurrent.Executors + +private object CallbackWorkerBasic { + // [START android_background_callback_worker] + class CallbackWorker( + context: Context, + params: WorkerParameters + ) : ListenableWorker(context, params) { + override fun startWork(): ListenableFuture { + return CallbackToFutureAdapter.getFuture { completer -> + val callback = object : Callback { + var successes = 0 + + override fun onFailure(call: Call, e: IOException) { + completer.setException(e) + } + + override fun onResponse(call: Call, response: Response) { + successes++ + if (successes == 100) { + completer.set(Result.success()) + } + } + } + + repeat(100) { + downloadAsynchronously("https://example.com", callback) + } + + callback + } + } + } + // [END android_background_callback_worker] +} + +private object CallbackWorkerCancellation { + // [START android_background_callback_worker_cancellation] + class CallbackWorker( + context: Context, + params: WorkerParameters + ) : ListenableWorker(context, params) { + override fun startWork(): ListenableFuture { + return CallbackToFutureAdapter.getFuture { completer -> + val callback = object : Callback { + var successes = 0 + + override fun onFailure(call: Call, e: IOException) { + completer.setException(e) + } + + override fun onResponse(call: Call, response: Response) { + ++successes + if (successes == 100) { + completer.set(Result.success()) + } + } + } + + completer.addCancellationListener(cancelDownloadsRunnable, executor) + + repeat(100) { + downloadAsynchronously("https://example.com", callback) + } + + callback + } + } + } + // [END android_background_callback_worker_cancellation] +} + +private fun remoteListenableWorkerRequest(): OneTimeWorkRequest { + // [START android_background_remote_listenable_worker_request] + val PACKAGE_NAME = "com.example.background.multiprocess" + + val serviceName = RemoteWorkerService::class.java.name + val componentName = ComponentName(PACKAGE_NAME, serviceName) + + val data: Data = Data.Builder() + .putString(ARGUMENT_PACKAGE_NAME, componentName.packageName) + .putString(ARGUMENT_CLASS_NAME, componentName.className) + .build() + + return OneTimeWorkRequest.Builder(ExampleRemoteListenableWorker::class.java) + .setInputData(data) + .build() + // [END android_background_remote_listenable_worker_request] +} + +private interface Callback { + fun onFailure(call: Call, e: IOException) + fun onResponse(call: Call, response: Response) +} + +private class Call +private class Response + +private fun downloadAsynchronously(url: String, callback: Callback) {} +private val cancelDownloadsRunnable = Runnable {} +private val executor: Executor = Executors.newSingleThreadExecutor() + +class ExampleRemoteListenableWorker( + context: Context, + parameters: WorkerParameters +) : RemoteListenableWorker(context, parameters) { + override fun startRemoteWork(): ListenableFuture = + CallbackToFutureAdapter.getFuture { it.set(Result.success()) } +} diff --git a/backgroundwork/src/main/java/com/example/snippets/backgroundwork/LongRunningWorker.kt b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/LongRunningWorker.kt new file mode 100644 index 000000000..78677a9cd --- /dev/null +++ b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/LongRunningWorker.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.backgroundwork + +import android.annotation.SuppressLint +import android.app.Notification +import android.app.NotificationManager +import android.content.Context +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import androidx.work.CoroutineWorker +import androidx.work.ForegroundInfo +import androidx.work.WorkManager +import androidx.work.WorkerParameters + +@SuppressLint("ObsoleteSdkInt") +// [START android_background_long_running_coroutine_worker] +class DownloadWorker(context: Context, parameters: WorkerParameters) : + CoroutineWorker(context, parameters) { + + private val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as + NotificationManager + + // [START_EXCLUDE silent] + private val notificationId = 1 + // [END_EXCLUDE] + override suspend fun doWork(): Result { + val inputUrl = inputData.getString(KEY_INPUT_URL) + ?: return Result.failure() + val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME) + ?: return Result.failure() + // Mark the Worker as important. + val progress = "Starting Download" + setForeground(createForegroundInfo(progress)) + download(inputUrl, outputFile) + return Result.success() + } + + private fun download(inputUrl: String, outputFile: String) { + // Downloads a file and updates bytes read. + // Calls setForeground() periodically when it needs to update + // the ongoing Notification. + } + // Creates an instance of ForegroundInfo which can be used to update the + // ongoing notification. + private fun createForegroundInfo(progress: String): ForegroundInfo { + val id = applicationContext.getString(R.string.notification_channel_id) + val title = applicationContext.getString(R.string.notification_title) + val cancel = applicationContext.getString(R.string.cancel_download) + // This PendingIntent can be used to cancel the worker. + val intent = WorkManager.getInstance(applicationContext) + .createCancelPendingIntent(getId()) + + // Create a Notification channel if necessary. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel() + } + + val notification = NotificationCompat.Builder(applicationContext, id) + .setContentTitle(title) + .setTicker(title) + .setContentText(progress) + .setSmallIcon(R.drawable.ic_work_notification) + .setOngoing(true) + // Add the cancel action to the notification which can + // be used to cancel the worker. + .addAction(android.R.drawable.ic_delete, cancel, intent) + .build() + + return ForegroundInfo(notificationId, notification) + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createChannel() { + // Create a Notification channel. + } + + companion object { + const val KEY_INPUT_URL = "KEY_INPUT_URL" + const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME" + } +} +// [END android_background_long_running_coroutine_worker] + +class ForegroundServiceTypeSnippet( + context: Context, + parameters: WorkerParameters +) : CoroutineWorker(context, parameters) { + override suspend fun doWork(): Result = Result.success() + + private val NOTIFICATION_ID = 1 + private lateinit var notification: Notification + + // [START android_background_long_running_foreground_service_type] + private fun createForegroundInfo(progress: String): ForegroundInfo { + // ... + return ForegroundInfo( + NOTIFICATION_ID, + notification, + FOREGROUND_SERVICE_TYPE_LOCATION or + FOREGROUND_SERVICE_TYPE_MICROPHONE + ) + } + // [END android_background_long_running_foreground_service_type] +} diff --git a/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ManageWork.kt b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ManageWork.kt new file mode 100644 index 000000000..a106bed20 --- /dev/null +++ b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ManageWork.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.backgroundwork + +import android.content.Context +import android.view.View +import androidx.work.Constraints +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkQuery +import androidx.work.WorkRequest +import androidx.work.Worker +import androidx.work.WorkerParameters +import com.google.android.material.snackbar.Snackbar +import com.google.common.util.concurrent.ListenableFuture +import java.util.concurrent.TimeUnit + +private fun manageWorkEnqueue(requireContext: () -> Context) { + // [START android_background_manage_work_enqueue] + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + val myWork: WorkRequest = // ... OneTime or PeriodicWork. + // [START_EXCLUDE silent] + */ + val myWork: WorkRequest = OneTimeWorkRequestBuilder().build() + // [END_EXCLUDE] + WorkManager.getInstance(requireContext()).enqueue(myWork) + // [END android_background_manage_work_enqueue] +} + +private fun Context.uniquePeriodicWork() { + // [START android_background_unique_periodic_work] + val sendLogsWorkRequest = + PeriodicWorkRequestBuilder(24, TimeUnit.HOURS) + .setConstraints( + Constraints.Builder() + .setRequiresCharging(true) + .build() + ) + .build() + WorkManager.getInstance(this).enqueueUniquePeriodicWork( + "sendLogs", + ExistingPeriodicWorkPolicy.KEEP, + sendLogsWorkRequest + ) + // [END android_background_unique_periodic_work] +} + +private fun observeWorkQuery(workManager: WorkManager, syncWorker: WorkRequest) { + // [START android_background_observe_work_query] + // by id. + workManager.getWorkInfoById(syncWorker.id) // ListenableFuture + + // by name. + workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture> + + // by tag. + workManager.getWorkInfosByTag("syncTag") // ListenableFuture> + // [END android_background_observe_work_query] +} + +private suspend fun observeWorkFlow( + workManager: WorkManager, + syncWorker: WorkRequest, + requireView: () -> View +) { + // [START android_background_observe_work_flow] + workManager.getWorkInfoByIdFlow(syncWorker.id) + .collect { workInfo -> + if (workInfo?.state == WorkInfo.State.SUCCEEDED) { + Snackbar.make( + requireView(), + R.string.work_completed, Snackbar.LENGTH_SHORT + ) + .show() + } + } + // [END android_background_observe_work_flow] +} + +private fun complexWorkQueries(workManager: WorkManager) { + // [START android_background_complex_work_queries] + val workQuery = WorkQuery.Builder + .fromTags(listOf("syncTag")) + .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED)) + .addUniqueWorkNames( + listOf("preProcess", "sync") + ) + .build() + + val workInfos: ListenableFuture> = workManager.getWorkInfos(workQuery) + // [END android_background_complex_work_queries] +} + +private fun cancelWork(workManager: WorkManager, syncWorker: WorkRequest) { + // [START android_background_cancel_work] + // by id. + workManager.cancelWorkById(syncWorker.id) + + // by name. + workManager.cancelUniqueWork("sync") + + // by tag. + workManager.cancelAllWorkByTag("syncTag") + // [END android_background_cancel_work] +} + +class SendLogsWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + override fun doWork(): Result = Result.success() +} diff --git a/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ObserveWork.kt b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ObserveWork.kt new file mode 100644 index 000000000..dbc2748d6 --- /dev/null +++ b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/ObserveWork.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.backgroundwork + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkRequest +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import java.util.UUID +import kotlinx.coroutines.delay + +// [START android_background_observe_progress_worker] +// import android.content.Context +// import androidx.work.CoroutineWorker +// import androidx.work.WorkerParameters +// import kotlinx.coroutines.delay + +class ProgressWorker(context: Context, parameters: WorkerParameters) : + CoroutineWorker(context, parameters) { + + companion object { + const val Progress = "Progress" + private const val delayDuration = 1L + } + + override suspend fun doWork(): Result { + val firstUpdate = workDataOf(Progress to 0) + val lastUpdate = workDataOf(Progress to 100) + setProgress(firstUpdate) + delay(delayDuration) + setProgress(lastUpdate) + return Result.success() + } +} +// [END android_background_observe_progress_worker] + +private suspend fun observeProgressFlow(applicationContext: Context, requestId: UUID) { + // [START android_background_observe_progress_flow] + WorkManager.getInstance(applicationContext) + // requestId is the WorkRequest id. + .getWorkInfoByIdFlow(requestId) + .collect { workInfo: WorkInfo? -> + if (workInfo != null) { + val progress = workInfo.progress + val value = progress.getInt("Progress", 0) + // Do something with progress information. + } + } + // [END android_background_observe_progress_flow] +} + +private suspend fun observeStopReason(workManager: WorkManager, syncWorker: WorkRequest) { + // [START android_background_observe_stop_reason] + workManager.getWorkInfoByIdFlow(syncWorker.id) + .collect { workInfo -> + if (workInfo != null) { + val stopReason = workInfo.stopReason + logStopReason(syncWorker.id, stopReason) + } + } + // [END android_background_observe_stop_reason] +} + +private fun logStopReason(id: UUID, stopReason: Int) {} diff --git a/backgroundwork/src/main/java/com/example/snippets/backgroundwork/UpdateWork.kt b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/UpdateWork.kt new file mode 100644 index 000000000..265163e5f --- /dev/null +++ b/backgroundwork/src/main/java/com/example/snippets/backgroundwork/UpdateWork.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.backgroundwork + +import android.content.Context +import androidx.concurrent.futures.await +import androidx.work.Constraints +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkRequest +import androidx.work.Worker +import androidx.work.WorkerParameters +import java.util.UUID + +private const val PHOTO_UPLOAD_WORK_NAME = "photo_upload" + +// [START android_background_update_photo_upload_work] +suspend fun updatePhotoUploadWork(context: Context) { + // Get instance of WorkManager. + val workManager = WorkManager.getInstance(context) + + // Retrieve the work request ID. In this example, the work being updated is unique + // work so we can retrieve the ID using the unique work name. + val photoUploadWorkInfoList = workManager.getWorkInfosForUniqueWork( + PHOTO_UPLOAD_WORK_NAME + ).await() + + val existingWorkRequestId = photoUploadWorkInfoList.firstOrNull()?.id ?: return + + // Update the constraints of the WorkRequest to not require a charging device. + val newConstraints = Constraints.Builder() + // Add other constraints as required here. + .setRequiresCharging(false) + .build() + + // Create new WorkRequest from existing Worker, new constraints, and the id of the old WorkRequest. + val updatedWorkRequest: WorkRequest = + OneTimeWorkRequestBuilder() + .setConstraints(newConstraints) + .setId(existingWorkRequestId) + .build() + + // Pass the new WorkRequest to updateWork(). + workManager.updateWork(updatedWorkRequest) +} +// [END android_background_update_photo_upload_work] + +private fun trackWorkGeneration(context: Context, oldWorkRequestId: UUID) { + // [START android_background_track_work_generation] + // Get instance of WorkManager. + val workManager = WorkManager.getInstance(context) + + // Retrieve WorkInfo instance. + val workInfo = workManager.getWorkInfoById(oldWorkRequestId).get() + + // Retrieve the generation. + val generation = workInfo?.generation + // [END android_background_track_work_generation] +} + +class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + override fun doWork(): Result = Result.success() +} diff --git a/backgroundwork/src/main/res/drawable/ic_work_notification.xml b/backgroundwork/src/main/res/drawable/ic_work_notification.xml new file mode 100644 index 000000000..9d6286b2f --- /dev/null +++ b/backgroundwork/src/main/res/drawable/ic_work_notification.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/backgroundwork/src/main/res/values/strings.xml b/backgroundwork/src/main/res/values/strings.xml new file mode 100644 index 000000000..497dc72be --- /dev/null +++ b/backgroundwork/src/main/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + download_channel + Downloading + Cancel + Work completed + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39a34593d..e2fdd2089 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -286,6 +286,7 @@ androidx-window-core = { module = "androidx.window:window-core", version.ref = " androidx-window-java = { module = "androidx.window:window-java", version.ref = "androidx-window-java" } androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "androidx-work-runtime" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work-runtime" } +androidx-work-multiprocess = { module = "androidx.work:work-multiprocess", version.ref = "androidx-work-runtime" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } billing = { module = "com.android.billingclient:billing-ktx", version.ref = "playbilling" } camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera-camera2" }