1 /*
2  * Copyright (C) 2022 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 
17 package com.android.adservices.data.common;
18 
19 import android.adservices.common.AdSelectionSignals;
20 import android.adservices.common.AdTechIdentifier;
21 import android.net.Uri;
22 
23 import androidx.annotation.Nullable;
24 import androidx.room.TypeConverter;
25 
26 import com.android.adservices.LoggerFactory;
27 
28 import org.json.JSONArray;
29 
30 import java.time.Instant;
31 import java.util.HashSet;
32 import java.util.Optional;
33 import java.util.Set;
34 
35 /**
36  * Room DB type converters for FLEDGE.
37  *
38  * <p>Register custom type converters here.
39  */
40 public class FledgeRoomConverters {
41     private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
42 
FledgeRoomConverters()43     private FledgeRoomConverters() {}
44 
45     /** Serialize {@link Instant} to Long. */
46     @TypeConverter
47     @Nullable
serializeInstant(@ullable Instant instant)48     public static Long serializeInstant(@Nullable Instant instant) {
49         return Optional.ofNullable(instant).map(Instant::toEpochMilli).orElse(null);
50     }
51 
52     /** Deserialize {@link Instant} from long. */
53     @TypeConverter
54     @Nullable
deserializeInstant(@ullable Long epochMilli)55     public static Instant deserializeInstant(@Nullable Long epochMilli) {
56         return Optional.ofNullable(epochMilli).map(Instant::ofEpochMilli).orElse(null);
57     }
58 
59     /** Deserialize {@link Uri} from String. */
60     @TypeConverter
61     @Nullable
deserializeUri(@ullable String uri)62     public static Uri deserializeUri(@Nullable String uri) {
63         return Optional.ofNullable(uri).map(Uri::parse).orElse(null);
64     }
65 
66     /** Serialize {@link Uri} to String. */
67     @TypeConverter
68     @Nullable
serializeUri(@ullable Uri uri)69     public static String serializeUri(@Nullable Uri uri) {
70         return Optional.ofNullable(uri).map(Uri::toString).orElse(null);
71     }
72 
73     /** Serialize an {@link AdTechIdentifier} to String. */
74     @TypeConverter
75     @Nullable
serializeAdTechIdentifier(@ullable AdTechIdentifier adTechIdentifier)76     public static String serializeAdTechIdentifier(@Nullable AdTechIdentifier adTechIdentifier) {
77         return Optional.ofNullable(adTechIdentifier).map(AdTechIdentifier::toString).orElse(null);
78     }
79 
80     /** Deserialize an {@link AdTechIdentifier} from a String. */
81     @TypeConverter
82     @Nullable
deserializeAdTechIdentifier(@ullable String adTechIdentifier)83     public static AdTechIdentifier deserializeAdTechIdentifier(@Nullable String adTechIdentifier) {
84         return Optional.ofNullable(adTechIdentifier).map(AdTechIdentifier::fromString).orElse(null);
85     }
86 
87     /** Serialize an {@link AdSelectionSignals} to String. */
88     @TypeConverter
89     @Nullable
serializeAdSelectionSignals(@ullable AdSelectionSignals signals)90     public static String serializeAdSelectionSignals(@Nullable AdSelectionSignals signals) {
91         return Optional.ofNullable(signals).map(AdSelectionSignals::toString).orElse(null);
92     }
93 
94     /** Deserialize an {@link AdSelectionSignals} from a String. */
95     @TypeConverter
96     @Nullable
deserializeAdSelectionSignals(@ullable String signals)97     public static AdSelectionSignals deserializeAdSelectionSignals(@Nullable String signals) {
98         return Optional.ofNullable(signals).map(AdSelectionSignals::fromString).orElse(null);
99     }
100 
101     /** Serialize a {@link Set} of Strings into a JSON array as a String. */
102     @TypeConverter
103     @Nullable
serializeStringSet(@ullable Set<String> stringSet)104     public static String serializeStringSet(@Nullable Set<String> stringSet) {
105         if (stringSet == null) {
106             return null;
107         }
108 
109         JSONArray jsonSet = new JSONArray(stringSet);
110         return jsonSet.toString();
111     }
112 
113     /** Deserialize a {@link Set} of Strings from a JSON array. */
114     @TypeConverter
115     @Nullable
deserializeStringSet(@ullable String serializedSet)116     public static Set<String> deserializeStringSet(@Nullable String serializedSet) {
117         if (serializedSet == null) {
118             return null;
119         }
120 
121         Set<String> outputSet = new HashSet<>();
122         JSONArray jsonSet;
123         try {
124             jsonSet = new JSONArray(serializedSet);
125         } catch (Exception exception) {
126             sLogger.d(exception, "Error deserializing set of strings from DB; returning null set");
127             return null;
128         }
129 
130         for (int arrayIndex = 0; arrayIndex < jsonSet.length(); arrayIndex++) {
131             String currentString;
132             try {
133                 currentString = jsonSet.getString(arrayIndex);
134             } catch (Exception exception) {
135                 // getString() coerces elements into Strings, so this should only happen if we get
136                 // out of bounds
137                 sLogger.d(
138                         exception,
139                         "Error deserializing set string #%d from DB; skipping any other elements",
140                         arrayIndex);
141                 break;
142             }
143             outputSet.add(currentString);
144         }
145 
146         return outputSet;
147     }
148 
149     /** Serialize a {@link Set} of Integers into a JSON array as a String. */
150     @TypeConverter
151     @Nullable
serializeIntegerSet(@ullable Set<Integer> integerSet)152     public static String serializeIntegerSet(@Nullable Set<Integer> integerSet) {
153         if (integerSet == null) {
154             return null;
155         }
156 
157         JSONArray jsonSet = new JSONArray(integerSet);
158         return jsonSet.toString();
159     }
160 
161     /** Deserialize a {@link Set} of Strings from a JSON array. */
162     @TypeConverter
163     @Nullable
deserializeIntegerSet(@ullable String serializedSet)164     public static Set<Integer> deserializeIntegerSet(@Nullable String serializedSet) {
165         if (serializedSet == null) {
166             return null;
167         }
168 
169         Set<Integer> outputSet = new HashSet<>();
170         JSONArray jsonSet;
171         try {
172             jsonSet = new JSONArray(serializedSet);
173         } catch (Exception exception) {
174             sLogger.d(exception, "Error deserializing set of ints from DB; returning null set");
175             return null;
176         }
177 
178         for (int arrayIndex = 0; arrayIndex < jsonSet.length(); arrayIndex++) {
179             int currentInt;
180             try {
181                 currentInt = jsonSet.getInt(arrayIndex);
182             } catch (Exception exception) {
183                 sLogger.d(
184                         exception,
185                         "Error deserializing set int #%d from DB; skipping element from %s",
186                         arrayIndex,
187                         serializedSet);
188                 continue;
189             }
190             outputSet.add(currentInt);
191         }
192 
193         return outputSet;
194     }
195 }
196