1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package androidx.work
17 
18 import android.content.Context
19 import androidx.annotation.RestrictTo
20 
21 /**
22  * A factory object that creates [ListenableWorker] instances. The factory is invoked every time a
23  * work runs. You can override the default implementation of this factory by manually initializing
24  * [WorkManager] (see [WorkManager.initialize] and specifying a new WorkerFactory in
25  * [Configuration.Builder.setWorkerFactory].
26  */
27 abstract class WorkerFactory {
28     /**
29      * Override this method to implement your custom worker-creation logic. Use
30      * [Configuration.Builder.setWorkerFactory] to use your custom class.
31      *
32      * Throwing an [Exception] here and no [ListenableWorker] will be created. If a [WorkerFactory]
33      * is unable to create an instance of the [ListenableWorker], it should return `null` so it can
34      * delegate to the default [WorkerFactory].
35      *
36      * Returns a new instance of the specified `workerClassName` given the arguments. The returned
37      * worker must be a newly-created instance and must not have been previously returned or invoked
38      * by WorkManager. Otherwise, WorkManager will throw an [IllegalStateException].
39      *
40      * @param appContext The application context
41      * @param workerClassName The class name of the worker to create
42      * @param workerParameters Parameters for worker initialization
43      * @return A new [ListenableWorker] instance of type `workerClassName`, or `null` if the worker
44      *   could not be created
45      */
createWorkernull46     abstract fun createWorker(
47         appContext: Context,
48         workerClassName: String,
49         workerParameters: WorkerParameters
50     ): ListenableWorker?
51 
52     /**
53      * Returns a new instance of the specified `workerClassName` given the arguments. If no worker
54      * is found, default reflection-based code will be used to instantiate the worker with the
55      * current ClassLoader. The returned worker should be a newly-created instance and must not have
56      * been previously returned or used by WorkManager.
57      *
58      * @param appContext The application context
59      * @param workerClassName The class name of the worker to create
60      * @param workerParameters Parameters for worker initialization
61      * @return A new [ListenableWorker] instance of type `workerClassName`, or `null` if the worker
62      *   could not be created
63      * @throws IllegalStateException when `workerClassName` cannot be instantiated or the
64      *   [WorkerFactory] returns an instance of the [ListenableWorker] which is used.
65      */
66     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
67     fun createWorkerWithDefaultFallback(
68         appContext: Context,
69         workerClassName: String,
70         workerParameters: WorkerParameters
71     ): ListenableWorker {
72         fun getWorkerClass(workerClassName: String): Class<out ListenableWorker> {
73             return try {
74                 Class.forName(workerClassName).asSubclass(ListenableWorker::class.java)
75             } catch (throwable: Throwable) {
76                 Logger.get().error(TAG, "Invalid class: $workerClassName", throwable)
77                 throw throwable
78             }
79         }
80         fun fallbackToReflection(
81             workerClassName: String,
82             workerParameters: WorkerParameters
83         ): ListenableWorker {
84             val clazz = getWorkerClass(workerClassName)
85             return try {
86                 val constructor =
87                     clazz.getDeclaredConstructor(Context::class.java, WorkerParameters::class.java)
88                 constructor.newInstance(appContext, workerParameters)
89             } catch (e: Throwable) {
90                 Logger.get().error(TAG, "Could not instantiate $workerClassName", e)
91                 throw e
92             }
93         }
94         val worker =
95             createWorker(appContext, workerClassName, workerParameters)
96                 ?: fallbackToReflection(workerClassName, workerParameters)
97         if (worker.isUsed) {
98             val message =
99                 "WorkerFactory (${javaClass.name}) returned an instance of" +
100                     " a ListenableWorker ($workerClassName) which has already been invoked. " +
101                     "createWorker() must always return a new instance of a ListenableWorker."
102             throw IllegalStateException(message)
103         }
104         return worker
105     }
106 }
107 
108 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
109 object DefaultWorkerFactory : WorkerFactory() {
createWorkernull110     override fun createWorker(
111         appContext: Context,
112         workerClassName: String,
113         workerParameters: WorkerParameters
114     ) = null
115 }
116 
117 private val TAG = Logger.tagWithPrefix("WorkerFactory")
118