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