1 /*
<lambda>null2  * 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.impl.model
17 
18 import android.net.Uri
19 import android.os.Build
20 import androidx.room.TypeConverter
21 import androidx.work.BackoffPolicy
22 import androidx.work.Constraints
23 import androidx.work.Constraints.ContentUriTrigger
24 import androidx.work.NetworkType
25 import androidx.work.OutOfQuotaPolicy
26 import androidx.work.WorkInfo
27 import androidx.work.impl.utils.NetworkRequest28
28 import androidx.work.impl.utils.NetworkRequestCompat
29 import androidx.work.impl.utils.capabilitiesCompat
30 import androidx.work.impl.utils.transportTypesCompat
31 import java.io.ByteArrayInputStream
32 import java.io.ByteArrayOutputStream
33 import java.io.IOException
34 import java.io.ObjectInputStream
35 import java.io.ObjectOutputStream
36 
37 /** TypeConverters for WorkManager enums and classes. */
38 object WorkTypeConverters {
39     /** Integer identifiers that map to [WorkInfo.State]. */
40     object StateIds {
41         const val ENQUEUED = 0
42         const val RUNNING = 1
43         const val SUCCEEDED = 2
44         const val FAILED = 3
45         const val BLOCKED = 4
46         const val CANCELLED = 5
47         const val COMPLETED_STATES = "($SUCCEEDED, $FAILED, $CANCELLED)"
48     }
49 
50     /** Integer identifiers that map to [BackoffPolicy]. */
51     private object BackoffPolicyIds {
52         const val EXPONENTIAL = 0
53         const val LINEAR = 1
54     }
55 
56     /** Integer identifiers that map to [NetworkType]. */
57     private object NetworkTypeIds {
58         const val NOT_REQUIRED = 0
59         const val CONNECTED = 1
60         const val UNMETERED = 2
61         const val NOT_ROAMING = 3
62         const val METERED = 4
63         const val TEMPORARILY_UNMETERED = 5
64     }
65 
66     /** Integer identifiers that map to [OutOfQuotaPolicy]. */
67     private object OutOfPolicyIds {
68         const val RUN_AS_NON_EXPEDITED_WORK_REQUEST = 0
69         const val DROP_WORK_REQUEST = 1
70     }
71 
72     /**
73      * TypeConverter for a State to an int.
74      *
75      * @param state The input State
76      * @return The associated int constant
77      */
78     @JvmStatic
79     @TypeConverter
80     fun stateToInt(state: WorkInfo.State): Int {
81         return when (state) {
82             WorkInfo.State.ENQUEUED -> StateIds.ENQUEUED
83             WorkInfo.State.RUNNING -> StateIds.RUNNING
84             WorkInfo.State.SUCCEEDED -> StateIds.SUCCEEDED
85             WorkInfo.State.FAILED -> StateIds.FAILED
86             WorkInfo.State.BLOCKED -> StateIds.BLOCKED
87             WorkInfo.State.CANCELLED -> StateIds.CANCELLED
88         }
89     }
90 
91     /**
92      * TypeConverter for an int to a State.
93      *
94      * @param value The input integer
95      * @return The associated State enum value
96      */
97     @JvmStatic
98     @TypeConverter
99     fun intToState(value: Int): WorkInfo.State {
100         return when (value) {
101             StateIds.ENQUEUED -> WorkInfo.State.ENQUEUED
102             StateIds.RUNNING -> WorkInfo.State.RUNNING
103             StateIds.SUCCEEDED -> WorkInfo.State.SUCCEEDED
104             StateIds.FAILED -> WorkInfo.State.FAILED
105             StateIds.BLOCKED -> WorkInfo.State.BLOCKED
106             StateIds.CANCELLED -> WorkInfo.State.CANCELLED
107             else -> throw IllegalArgumentException("Could not convert $value to State")
108         }
109     }
110 
111     /**
112      * TypeConverter for a BackoffPolicy to an int.
113      *
114      * @param backoffPolicy The input BackoffPolicy
115      * @return The associated int constant
116      */
117     @JvmStatic
118     @TypeConverter
119     fun backoffPolicyToInt(backoffPolicy: BackoffPolicy): Int {
120         return when (backoffPolicy) {
121             BackoffPolicy.EXPONENTIAL -> BackoffPolicyIds.EXPONENTIAL
122             BackoffPolicy.LINEAR -> BackoffPolicyIds.LINEAR
123         }
124     }
125 
126     /**
127      * TypeConverter for an int to a BackoffPolicy.
128      *
129      * @param value The input integer
130      * @return The associated BackoffPolicy enum value
131      */
132     @JvmStatic
133     @TypeConverter
134     fun intToBackoffPolicy(value: Int): BackoffPolicy {
135         return when (value) {
136             BackoffPolicyIds.EXPONENTIAL -> BackoffPolicy.EXPONENTIAL
137             BackoffPolicyIds.LINEAR -> BackoffPolicy.LINEAR
138             else -> throw IllegalArgumentException("Could not convert $value to BackoffPolicy")
139         }
140     }
141 
142     /**
143      * TypeConverter for a NetworkType to an int.
144      *
145      * @param networkType The input NetworkType
146      * @return The associated int constant
147      */
148     @JvmStatic
149     @TypeConverter
150     fun networkTypeToInt(networkType: NetworkType): Int {
151         return when (networkType) {
152             NetworkType.NOT_REQUIRED -> NetworkTypeIds.NOT_REQUIRED
153             NetworkType.CONNECTED -> NetworkTypeIds.CONNECTED
154             NetworkType.UNMETERED -> NetworkTypeIds.UNMETERED
155             NetworkType.NOT_ROAMING -> NetworkTypeIds.NOT_ROAMING
156             NetworkType.METERED -> NetworkTypeIds.METERED
157             else -> {
158                 if (Build.VERSION.SDK_INT >= 30 && networkType == NetworkType.TEMPORARILY_UNMETERED)
159                     NetworkTypeIds.TEMPORARILY_UNMETERED
160                 else throw IllegalArgumentException("Could not convert $networkType to int")
161             }
162         }
163     }
164 
165     /**
166      * TypeConverter for an int to a NetworkType.
167      *
168      * @param value The input integer
169      * @return The associated NetworkType enum value
170      */
171     @JvmStatic
172     @TypeConverter
173     fun intToNetworkType(value: Int): NetworkType {
174         return when (value) {
175             NetworkTypeIds.NOT_REQUIRED -> NetworkType.NOT_REQUIRED
176             NetworkTypeIds.CONNECTED -> NetworkType.CONNECTED
177             NetworkTypeIds.UNMETERED -> NetworkType.UNMETERED
178             NetworkTypeIds.NOT_ROAMING -> NetworkType.NOT_ROAMING
179             NetworkTypeIds.METERED -> NetworkType.METERED
180             else -> {
181                 if (Build.VERSION.SDK_INT >= 30 && value == NetworkTypeIds.TEMPORARILY_UNMETERED) {
182                     return NetworkType.TEMPORARILY_UNMETERED
183                 } else throw IllegalArgumentException("Could not convert $value to NetworkType")
184             }
185         }
186     }
187 
188     /**
189      * Converts a [OutOfQuotaPolicy] to an int.
190      *
191      * @param policy The [OutOfQuotaPolicy] policy being used
192      * @return the corresponding int representation.
193      */
194     @JvmStatic
195     @TypeConverter
196     fun outOfQuotaPolicyToInt(policy: OutOfQuotaPolicy): Int {
197         return when (policy) {
198             OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST ->
199                 OutOfPolicyIds.RUN_AS_NON_EXPEDITED_WORK_REQUEST
200             OutOfQuotaPolicy.DROP_WORK_REQUEST -> OutOfPolicyIds.DROP_WORK_REQUEST
201         }
202     }
203 
204     /**
205      * Converter from an int to a [OutOfQuotaPolicy].
206      *
207      * @param value The input integer
208      * @return An [OutOfQuotaPolicy]
209      */
210     @JvmStatic
211     @TypeConverter
212     fun intToOutOfQuotaPolicy(value: Int): OutOfQuotaPolicy {
213         return when (value) {
214             OutOfPolicyIds.RUN_AS_NON_EXPEDITED_WORK_REQUEST ->
215                 OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
216             OutOfPolicyIds.DROP_WORK_REQUEST -> OutOfQuotaPolicy.DROP_WORK_REQUEST
217             else -> throw IllegalArgumentException("Could not convert $value to OutOfQuotaPolicy")
218         }
219     }
220 
221     /**
222      * Converts a set of [Constraints.ContentUriTrigger]s to byte array representation
223      *
224      * @param triggers the list of [Constraints.ContentUriTrigger]s to convert
225      * @return corresponding byte array representation
226      */
227     @JvmStatic
228     @TypeConverter
229     fun setOfTriggersToByteArray(triggers: Set<ContentUriTrigger>): ByteArray {
230         if (triggers.isEmpty()) {
231             return ByteArray(0)
232         }
233         val outputStream = ByteArrayOutputStream()
234         outputStream.use {
235             ObjectOutputStream(outputStream).use { objectOutputStream ->
236                 objectOutputStream.writeInt(triggers.size)
237                 for (trigger in triggers) {
238                     objectOutputStream.writeUTF(trigger.uri.toString())
239                     objectOutputStream.writeBoolean(trigger.isTriggeredForDescendants)
240                 }
241             }
242         }
243         return outputStream.toByteArray()
244     }
245 
246     /**
247      * Converts a byte array to set of [ContentUriTrigger]s
248      *
249      * @param bytes byte array representation to convert
250      * @return set of [ContentUriTrigger]
251      */
252     @JvmStatic
253     @TypeConverter
254     fun byteArrayToSetOfTriggers(bytes: ByteArray): Set<ContentUriTrigger> {
255         val triggers = mutableSetOf<ContentUriTrigger>()
256         if (bytes.isEmpty()) {
257             // bytes will be null if there are no Content Uri Triggers
258             return triggers
259         }
260         val inputStream = ByteArrayInputStream(bytes)
261         inputStream.use {
262             try {
263                 ObjectInputStream(inputStream).use { objectInputStream ->
264                     repeat(objectInputStream.readInt()) {
265                         val uri = Uri.parse(objectInputStream.readUTF())
266                         val triggersForDescendants = objectInputStream.readBoolean()
267                         triggers.add(ContentUriTrigger(uri, triggersForDescendants))
268                     }
269                 }
270             } catch (e: IOException) {
271                 e.printStackTrace()
272             }
273         }
274         return triggers
275     }
276 
277     @JvmStatic
278     @TypeConverter
279     internal fun toNetworkRequest(bytes: ByteArray): NetworkRequestCompat {
280         if (Build.VERSION.SDK_INT < 28 || bytes.isEmpty()) {
281             return NetworkRequestCompat(null)
282         }
283         return ByteArrayInputStream(bytes).use {
284             ObjectInputStream(it).use { inputStream ->
285                 val transports = IntArray(inputStream.readInt())
286                 repeat(transports.size) { i -> transports[i] = inputStream.readInt() }
287                 val capabilities = IntArray(inputStream.readInt())
288                 repeat(capabilities.size) { i -> capabilities[i] = inputStream.readInt() }
289                 NetworkRequest28.createNetworkRequestCompat(capabilities, transports)
290             }
291         }
292     }
293 
294     @JvmStatic
295     @TypeConverter
296     internal fun fromNetworkRequest(requestCompat: NetworkRequestCompat): ByteArray {
297         if (Build.VERSION.SDK_INT < 28) {
298             return ByteArray(0)
299         }
300         val request = requestCompat.networkRequest ?: return ByteArray(0)
301         val outputStream = ByteArrayOutputStream()
302         outputStream.use {
303             ObjectOutputStream(it).use { outputStream ->
304                 val transports = request.transportTypesCompat
305                 val capabilities = request.capabilitiesCompat
306                 outputStream.writeInt(transports.size)
307                 transports.forEach { t -> outputStream.writeInt(t) }
308                 outputStream.writeInt(capabilities.size)
309                 capabilities.forEach { c -> outputStream.writeInt(c) }
310             }
311         }
312         return outputStream.toByteArray()
313     }
314 }
315