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.customaudience;
18 
19 import static android.adservices.customaudience.CustomAudience.FLAG_AUCTION_SERVER_REQUEST_DEFAULT;
20 
21 import android.adservices.common.AdSelectionSignals;
22 import android.adservices.common.AdTechIdentifier;
23 import android.adservices.customaudience.CustomAudience;
24 import android.net.Uri;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 import androidx.room.ColumnInfo;
29 import androidx.room.Embedded;
30 import androidx.room.Entity;
31 import androidx.room.ProvidedTypeConverter;
32 import androidx.room.TypeConverter;
33 import androidx.room.TypeConverters;
34 
35 import com.android.adservices.data.common.DBAdData;
36 import com.android.adservices.service.customaudience.CustomAudienceUpdatableData;
37 import com.android.adservices.service.profiling.Tracing;
38 import com.android.internal.util.Preconditions;
39 
40 import org.json.JSONArray;
41 import org.json.JSONException;
42 import org.json.JSONObject;
43 
44 import java.time.Duration;
45 import java.time.Instant;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.Optional;
50 import java.util.stream.Collectors;
51 
52 /**
53  * POJO represents a Custom Audience Database Entity. TODO: Align on the class naming strategy.
54  * (b/228095626)
55  */
56 @Entity(
57         tableName = DBCustomAudience.TABLE_NAME,
58         primaryKeys = {"owner", "buyer", "name"})
59 @TypeConverters({DBCustomAudience.Converters.class})
60 public class DBCustomAudience {
61     public static final String TABLE_NAME = "custom_audience";
62 
63     @ColumnInfo(name = "owner", index = true)
64     @NonNull
65     private final String mOwner;
66 
67     @ColumnInfo(name = "buyer", index = true)
68     @NonNull
69     private final AdTechIdentifier mBuyer;
70 
71     @ColumnInfo(name = "name")
72     @NonNull
73     private final String mName;
74 
75     @ColumnInfo(name = "expiration_time", index = true)
76     @NonNull
77     private final Instant mExpirationTime;
78 
79     // TODO(b/234429221): Investigate and decide if should add an index on the activation_time.
80     @ColumnInfo(name = "activation_time")
81     @NonNull
82     private final Instant mActivationTime;
83 
84     @ColumnInfo(name = "creation_time")
85     @NonNull
86     private final Instant mCreationTime;
87 
88     @ColumnInfo(name = "last_ads_and_bidding_data_updated_time", index = true)
89     @NonNull
90     private final Instant mLastAdsAndBiddingDataUpdatedTime;
91 
92     @ColumnInfo(name = "user_bidding_signals")
93     @Nullable
94     private final AdSelectionSignals mUserBiddingSignals;
95 
96     @Embedded(prefix = "trusted_bidding_data_")
97     @Nullable
98     private final DBTrustedBiddingData mTrustedBiddingData;
99 
100     @ColumnInfo(name = "bidding_logic_uri")
101     @NonNull
102     private final Uri mBiddingLogicUri;
103 
104     @ColumnInfo(name = "ads")
105     @Nullable
106     private final List<DBAdData> mAds;
107 
108     @ColumnInfo(name = "debuggable", defaultValue = "0")
109     private final boolean mDebuggable;
110 
111     @ColumnInfo(name = "auction_server_request_flags", defaultValue = "0")
112     @CustomAudience.AuctionServerRequestFlag
113     private final int mAuctionServerRequestFlags;
114 
DBCustomAudience( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name, @NonNull Instant expirationTime, @NonNull Instant activationTime, @NonNull Instant creationTime, @NonNull Instant lastAdsAndBiddingDataUpdatedTime, @Nullable AdSelectionSignals userBiddingSignals, @Nullable DBTrustedBiddingData trustedBiddingData, @NonNull Uri biddingLogicUri, @Nullable List<DBAdData> ads, boolean debuggable, @CustomAudience.AuctionServerRequestFlag int auctionServerRequestFlags)115     public DBCustomAudience(
116             @NonNull String owner,
117             @NonNull AdTechIdentifier buyer,
118             @NonNull String name,
119             @NonNull Instant expirationTime,
120             @NonNull Instant activationTime,
121             @NonNull Instant creationTime,
122             @NonNull Instant lastAdsAndBiddingDataUpdatedTime,
123             @Nullable AdSelectionSignals userBiddingSignals,
124             @Nullable DBTrustedBiddingData trustedBiddingData,
125             @NonNull Uri biddingLogicUri,
126             @Nullable List<DBAdData> ads,
127             boolean debuggable,
128             @CustomAudience.AuctionServerRequestFlag int auctionServerRequestFlags) {
129         Preconditions.checkStringNotEmpty(owner, "Owner must be provided");
130         Objects.requireNonNull(buyer, "Buyer must be provided.");
131         Preconditions.checkStringNotEmpty(name, "Name must be provided");
132         Objects.requireNonNull(expirationTime, "Expiration time must be provided.");
133         Objects.requireNonNull(creationTime, "Creation time must be provided.");
134         Objects.requireNonNull(
135                 lastAdsAndBiddingDataUpdatedTime,
136                 "Last ads and bidding data updated time must be provided.");
137         Objects.requireNonNull(biddingLogicUri, "Bidding logic uri must be provided.");
138 
139         mOwner = owner;
140         mBuyer = buyer;
141         mName = name;
142         mExpirationTime = expirationTime;
143         mActivationTime = activationTime;
144         mCreationTime = creationTime;
145         mLastAdsAndBiddingDataUpdatedTime = lastAdsAndBiddingDataUpdatedTime;
146         mUserBiddingSignals = userBiddingSignals;
147         mTrustedBiddingData = trustedBiddingData;
148         mBiddingLogicUri = biddingLogicUri;
149         mAds = ads;
150         mDebuggable = debuggable;
151         mAuctionServerRequestFlags = auctionServerRequestFlags;
152     }
153 
154     /**
155      * Parse parcelable {@link CustomAudience} to storage model {@link DBCustomAudience}.
156      *
157      * @param parcelable the service model
158      * @param callerPackageName the String package name for the calling application, used as the
159      *     owner app identifier
160      * @param currentTime the timestamp when calling the method
161      * @param defaultExpireIn the default expiration from activation
162      * @param adDataConversionStrategy Strategy to convert ads from DB
163      * @param debuggable If the CA was created in a debuggable context
164      * @param auctionServerRequestFlagsEnabled If auction server request flags are enabled.
165      * @return storage model
166      */
167     @NonNull
fromServiceObject( @onNull CustomAudience parcelable, @NonNull String callerPackageName, @NonNull Instant currentTime, @NonNull Duration defaultExpireIn, @NonNull AdDataConversionStrategy adDataConversionStrategy, boolean debuggable, boolean auctionServerRequestFlagsEnabled)168     public static DBCustomAudience fromServiceObject(
169             @NonNull CustomAudience parcelable,
170             @NonNull String callerPackageName,
171             @NonNull Instant currentTime,
172             @NonNull Duration defaultExpireIn,
173             @NonNull AdDataConversionStrategy adDataConversionStrategy,
174             boolean debuggable,
175             boolean auctionServerRequestFlagsEnabled) {
176         Objects.requireNonNull(parcelable);
177         Objects.requireNonNull(callerPackageName);
178         Objects.requireNonNull(currentTime);
179         Objects.requireNonNull(defaultExpireIn);
180         Objects.requireNonNull(adDataConversionStrategy);
181 
182         // Setting default value to be currentTime.
183         // Make it easier at query for activated CAs.
184         Instant activationTime =
185                 Optional.ofNullable(parcelable.getActivationTime()).orElse(currentTime);
186         if (activationTime.isBefore(currentTime)) {
187             activationTime = currentTime;
188         }
189 
190         Instant expirationTime =
191                 Optional.ofNullable(parcelable.getExpirationTime())
192                         .orElse(activationTime.plus(defaultExpireIn));
193 
194         Instant lastAdsAndBiddingDataUpdatedTime =
195                 parcelable.getAds().isEmpty()
196                                 || parcelable.getTrustedBiddingData() == null
197                                 || parcelable.getUserBiddingSignals() == null
198                         ? Instant.EPOCH
199                         : currentTime;
200 
201         return new DBCustomAudience.Builder()
202                 .setName(parcelable.getName())
203                 .setBuyer(parcelable.getBuyer())
204                 .setOwner(callerPackageName)
205                 .setActivationTime(activationTime)
206                 .setCreationTime(currentTime)
207                 .setLastAdsAndBiddingDataUpdatedTime(lastAdsAndBiddingDataUpdatedTime)
208                 .setExpirationTime(expirationTime)
209                 .setBiddingLogicUri(parcelable.getBiddingLogicUri())
210                 .setTrustedBiddingData(
211                         DBTrustedBiddingData.fromServiceObject(parcelable.getTrustedBiddingData()))
212                 .setDebuggable(debuggable)
213                 .setAds(
214                         parcelable.getAds().isEmpty()
215                                 ? null
216                                 : parcelable.getAds().stream()
217                                         .map(
218                                                 parcelableAd ->
219                                                         adDataConversionStrategy
220                                                                 .fromServiceObject(parcelableAd)
221                                                                 .build())
222                                         .collect(Collectors.toList()))
223                 .setUserBiddingSignals(parcelable.getUserBiddingSignals())
224                 .setAuctionServerRequestFlags(
225                         auctionServerRequestFlagsEnabled
226                                 ? parcelable.getAuctionServerRequestFlags()
227                                 : FLAG_AUCTION_SERVER_REQUEST_DEFAULT)
228                 .build();
229     }
230 
231     // TODO(b/321092996) Update this once {@link CustomAudienceUpdatableData} is updated with the
232     // new field
233     /**
234      * Creates a copy of the current {@link DBCustomAudience} object updated with data from a {@link
235      * CustomAudienceUpdatableData} object.
236      */
237     @NonNull
copyWithUpdatableData( @onNull CustomAudienceUpdatableData updatableData)238     public DBCustomAudience copyWithUpdatableData(
239             @NonNull CustomAudienceUpdatableData updatableData) {
240         Objects.requireNonNull(updatableData);
241 
242         if (!updatableData.getContainsSuccessfulUpdate()) {
243             return this;
244         }
245 
246         DBCustomAudience.Builder customAudienceBuilder =
247                 new Builder(this)
248                         .setLastAdsAndBiddingDataUpdatedTime(
249                                 updatableData.getAttemptedUpdateTime());
250 
251         if (updatableData.getUserBiddingSignals() != null) {
252             customAudienceBuilder.setUserBiddingSignals(updatableData.getUserBiddingSignals());
253         }
254 
255         if (updatableData.getTrustedBiddingData() != null) {
256             customAudienceBuilder.setTrustedBiddingData(updatableData.getTrustedBiddingData());
257         }
258 
259         if (updatableData.getAds() != null) {
260             customAudienceBuilder.setAds(updatableData.getAds());
261         }
262 
263         return customAudienceBuilder.build();
264     }
265 
266     /** The package name of the App that adds the user to this custom audience. */
267     @NonNull
getOwner()268     public String getOwner() {
269         return mOwner;
270     }
271 
272     /**
273      * The ad-tech who can read this custom audience information and return back relevant ad
274      * information. This is expected to be the domain’s name used in biddingLogicUri and
275      * dailyUpdateUri.
276      *
277      * <p>Max length: 200 bytes
278      */
279     @NonNull
getBuyer()280     public AdTechIdentifier getBuyer() {
281         return mBuyer;
282     }
283 
284     /**
285      * Identifies the CustomAudience within the set of ones created for this combination of owner
286      * and buyer.
287      *
288      * <p>Max length: 200 bytes
289      */
290     @NonNull
getName()291     public String getName() {
292         return mName;
293     }
294 
295     /**
296      * Defines until when the CA end to be effective, this can be used to remove a user from this CA
297      * only after a defined period of time.
298      *
299      * <p>Default to be 60 days after activation(Pending product confirm).
300      *
301      * <p>Should be within 1 year since activation.
302      */
303     @NonNull
getExpirationTime()304     public Instant getExpirationTime() {
305         return mExpirationTime;
306     }
307 
308     /**
309      * Defines when the CA starts to be effective, this can be used to enroll a user to this CA only
310      * after a defined interval (for example to track the fact that the user has not been using the
311      * app in the last n days).
312      *
313      * <p>Should be within 1 year since creation.
314      */
315     @NonNull
getActivationTime()316     public Instant getActivationTime() {
317         return mActivationTime;
318     }
319 
320     /** Returns the time the CA was created. */
321     @NonNull
getCreationTime()322     public Instant getCreationTime() {
323         return mCreationTime;
324     }
325 
326     /** Returns the time the CA ads and bidding data was last updated. */
327     @NonNull
getLastAdsAndBiddingDataUpdatedTime()328     public Instant getLastAdsAndBiddingDataUpdatedTime() {
329         return mLastAdsAndBiddingDataUpdatedTime;
330     }
331 
332     /**
333      * Signals needed for any on-device bidding for remarketing Ads. For instance, an App might
334      * decide to store an embedding from their user features a model here while creating the custom
335      * audience.
336      */
337     @Nullable
getUserBiddingSignals()338     public AdSelectionSignals getUserBiddingSignals() {
339         return mUserBiddingSignals;
340     }
341 
342     /**
343      * An ad-tech can define what data needs to be fetched from a trusted server
344      * (trusted_bidding_keys) and where it should be fetched from (trusted_bidding_uri).
345      */
346     @Nullable
getTrustedBiddingData()347     public DBTrustedBiddingData getTrustedBiddingData() {
348         return mTrustedBiddingData;
349     }
350 
351     /** Returns the URI to fetch bidding logic js. */
352     @NonNull
getBiddingLogicUri()353     public Uri getBiddingLogicUri() {
354         return mBiddingLogicUri;
355     }
356 
357     /** Returns Ads metadata that used to render an ad. */
358     @Nullable
getAds()359     public List<DBAdData> getAds() {
360         return mAds;
361     }
362 
363     /** Returns if CA was created in a debuggable context. */
isDebuggable()364     public boolean isDebuggable() {
365         return mDebuggable;
366     }
367 
368     /** Returns the bitfield of auction server request flags. */
369     @CustomAudience.AuctionServerRequestFlag
getAuctionServerRequestFlags()370     public int getAuctionServerRequestFlags() {
371         return mAuctionServerRequestFlags;
372     }
373 
374     @Override
equals(Object o)375     public boolean equals(Object o) {
376         if (this == o) return true;
377         if (!(o instanceof DBCustomAudience)) return false;
378         DBCustomAudience that = (DBCustomAudience) o;
379         return mOwner.equals(that.mOwner)
380                 && mBuyer.equals(that.mBuyer)
381                 && mName.equals(that.mName)
382                 && mExpirationTime.equals(that.mExpirationTime)
383                 && mActivationTime.equals(that.mActivationTime)
384                 && mCreationTime.equals(that.mCreationTime)
385                 && mLastAdsAndBiddingDataUpdatedTime.equals(that.mLastAdsAndBiddingDataUpdatedTime)
386                 && Objects.equals(mUserBiddingSignals, that.mUserBiddingSignals)
387                 && Objects.equals(mTrustedBiddingData, that.mTrustedBiddingData)
388                 && mBiddingLogicUri.equals(that.mBiddingLogicUri)
389                 && Objects.equals(mAds, that.mAds)
390                 && mAuctionServerRequestFlags == that.mAuctionServerRequestFlags;
391     }
392 
393     @Override
hashCode()394     public int hashCode() {
395         return Objects.hash(
396                 mOwner,
397                 mBuyer,
398                 mName,
399                 mExpirationTime,
400                 mActivationTime,
401                 mCreationTime,
402                 mLastAdsAndBiddingDataUpdatedTime,
403                 mUserBiddingSignals,
404                 mTrustedBiddingData,
405                 mBiddingLogicUri,
406                 mAds,
407                 mDebuggable,
408                 mAuctionServerRequestFlags);
409     }
410 
411     /**
412      * @return a new builder instance created from this object's cloned data
413      * @hide
414      */
415     @NonNull
cloneToBuilder()416     public DBCustomAudience.Builder cloneToBuilder() {
417         return new DBCustomAudience.Builder()
418                 .setOwner(this.mOwner)
419                 .setBuyer(this.mBuyer)
420                 .setName(this.mName)
421                 .setExpirationTime(this.mExpirationTime)
422                 .setActivationTime(this.mActivationTime)
423                 .setCreationTime(this.mCreationTime)
424                 .setLastAdsAndBiddingDataUpdatedTime(this.mLastAdsAndBiddingDataUpdatedTime)
425                 .setUserBiddingSignals(this.mUserBiddingSignals)
426                 .setTrustedBiddingData(this.mTrustedBiddingData)
427                 .setBiddingLogicUri(this.mBiddingLogicUri)
428                 .setAds(this.mAds)
429                 .setDebuggable(this.mDebuggable)
430                 .setAuctionServerRequestFlags(this.mAuctionServerRequestFlags);
431     }
432 
433     @Override
toString()434     public String toString() {
435         return "DBCustomAudience{"
436                 + "mOwner='"
437                 + mOwner
438                 + '\''
439                 + ", mBuyer="
440                 + mBuyer
441                 + ", mName='"
442                 + mName
443                 + '\''
444                 + ", mExpirationTime="
445                 + mExpirationTime
446                 + ", mActivationTime="
447                 + mActivationTime
448                 + ", mCreationTime="
449                 + mCreationTime
450                 + ", mLastAdsAndBiddingDataUpdatedTime="
451                 + mLastAdsAndBiddingDataUpdatedTime
452                 + ", mUserBiddingSignals="
453                 + mUserBiddingSignals
454                 + ", mTrustedBiddingData="
455                 + mTrustedBiddingData
456                 + ", mBiddingLogicUri="
457                 + mBiddingLogicUri
458                 + ", mAds="
459                 + mAds
460                 + ", mDebuggable="
461                 + mDebuggable
462                 + ", mAuctionServerRequestFlags="
463                 + mAuctionServerRequestFlags
464                 + '}';
465     }
466 
467     /** Builder to construct a {@link DBCustomAudience}. */
468     public static final class Builder {
469         private String mOwner;
470         private AdTechIdentifier mBuyer;
471         private String mName;
472         private Instant mExpirationTime;
473         private Instant mActivationTime;
474         private Instant mCreationTime;
475         private Instant mLastAdsAndBiddingDataUpdatedTime;
476         private AdSelectionSignals mUserBiddingSignals;
477         private DBTrustedBiddingData mTrustedBiddingData;
478         private Uri mBiddingLogicUri;
479         private List<DBAdData> mAds;
480         private boolean mDebuggable;
481         @CustomAudience.AuctionServerRequestFlag private int mAuctionServerRequestFlags;
482 
Builder()483         public Builder() {}
484 
Builder(@onNull DBCustomAudience customAudience)485         public Builder(@NonNull DBCustomAudience customAudience) {
486             Objects.requireNonNull(customAudience, "Custom audience must not be null.");
487 
488             mOwner = customAudience.getOwner();
489             mBuyer = customAudience.getBuyer();
490             mName = customAudience.getName();
491             mExpirationTime = customAudience.getExpirationTime();
492             mActivationTime = customAudience.getActivationTime();
493             mCreationTime = customAudience.getCreationTime();
494             mLastAdsAndBiddingDataUpdatedTime =
495                     customAudience.getLastAdsAndBiddingDataUpdatedTime();
496             mUserBiddingSignals = customAudience.getUserBiddingSignals();
497             mTrustedBiddingData = customAudience.getTrustedBiddingData();
498             mBiddingLogicUri = customAudience.getBiddingLogicUri();
499             mAds = customAudience.getAds();
500             mDebuggable = customAudience.isDebuggable();
501             mAuctionServerRequestFlags = customAudience.getAuctionServerRequestFlags();
502         }
503 
504         /** See {@link #getOwner()} for detail. */
setOwner(String owner)505         public Builder setOwner(String owner) {
506             mOwner = owner;
507             return this;
508         }
509 
510         /** See {@link #getBuyer()} for detail. */
setBuyer(AdTechIdentifier buyer)511         public Builder setBuyer(AdTechIdentifier buyer) {
512             mBuyer = buyer;
513             return this;
514         }
515 
516         /** See {@link #getName()} for detail. */
setName(String name)517         public Builder setName(String name) {
518             mName = name;
519             return this;
520         }
521 
522         /** See {@link #getExpirationTime()} for detail. */
setExpirationTime(Instant expirationTime)523         public Builder setExpirationTime(Instant expirationTime) {
524             mExpirationTime = expirationTime;
525             return this;
526         }
527 
528         /** See {@link #getActivationTime()} for detail. */
setActivationTime(Instant activationTime)529         public Builder setActivationTime(Instant activationTime) {
530             mActivationTime = activationTime;
531             return this;
532         }
533 
534         /** See {@link #getCreationTime()} for detail. */
setCreationTime(Instant creationTime)535         public Builder setCreationTime(Instant creationTime) {
536             mCreationTime = creationTime;
537             return this;
538         }
539 
540         /** See {@link #getLastAdsAndBiddingDataUpdatedTime()} for detail. */
setLastAdsAndBiddingDataUpdatedTime( Instant lastAdsAndBiddingDataUpdatedTime)541         public Builder setLastAdsAndBiddingDataUpdatedTime(
542                 Instant lastAdsAndBiddingDataUpdatedTime) {
543             mLastAdsAndBiddingDataUpdatedTime = lastAdsAndBiddingDataUpdatedTime;
544             return this;
545         }
546 
547         /** See {@link #getUserBiddingSignals()} for detail. */
setUserBiddingSignals(AdSelectionSignals userBiddingSignals)548         public Builder setUserBiddingSignals(AdSelectionSignals userBiddingSignals) {
549             mUserBiddingSignals = userBiddingSignals;
550             return this;
551         }
552 
553         /** See {@link #getTrustedBiddingData()} for detail. */
setTrustedBiddingData(DBTrustedBiddingData trustedBiddingData)554         public Builder setTrustedBiddingData(DBTrustedBiddingData trustedBiddingData) {
555             mTrustedBiddingData = trustedBiddingData;
556             return this;
557         }
558 
559         /** See {@link #getBiddingLogicUri()} for detail. */
setBiddingLogicUri(Uri biddingLogicUri)560         public Builder setBiddingLogicUri(Uri biddingLogicUri) {
561             mBiddingLogicUri = biddingLogicUri;
562             return this;
563         }
564 
565         /** See {@link #getAds()} for detail. */
setAds(List<DBAdData> ads)566         public Builder setAds(List<DBAdData> ads) {
567             mAds = ads;
568             return this;
569         }
570 
571         /** See {@link #isDebuggable()} for detail. */
setDebuggable(boolean debuggable)572         public Builder setDebuggable(boolean debuggable) {
573             mDebuggable = debuggable;
574             return this;
575         }
576 
577         /** Sets the bitfield of auction server request flags. */
578         @NonNull
setAuctionServerRequestFlags( @ustomAudience.AuctionServerRequestFlag int auctionServerRequestFlags)579         public Builder setAuctionServerRequestFlags(
580                 @CustomAudience.AuctionServerRequestFlag int auctionServerRequestFlags) {
581             mAuctionServerRequestFlags = auctionServerRequestFlags;
582             return this;
583         }
584 
585         /**
586          * Build the {@link DBCustomAudience}.
587          *
588          * @return the built {@link DBCustomAudience}.
589          */
build()590         public DBCustomAudience build() {
591             return new DBCustomAudience(
592                     mOwner,
593                     mBuyer,
594                     mName,
595                     mExpirationTime,
596                     mActivationTime,
597                     mCreationTime,
598                     mLastAdsAndBiddingDataUpdatedTime,
599                     mUserBiddingSignals,
600                     mTrustedBiddingData,
601                     mBiddingLogicUri,
602                     mAds,
603                     mDebuggable,
604                     mAuctionServerRequestFlags);
605         }
606     }
607 
608     /**
609      * Room DB type converters.
610      *
611      * <p>Register custom type converters here.
612      *
613      * <p>{@link TypeConverter} registered here only apply to data access with {@link
614      * DBCustomAudience}
615      */
616     @ProvidedTypeConverter
617     public static class Converters {
618 
619         private final AdDataConversionStrategy mAdDataConversionStrategy;
620 
Converters( boolean frequencyCapFilteringEnabled, boolean appInstallFilteringEnabled, boolean adRenderIdEnabled)621         public Converters(
622                 boolean frequencyCapFilteringEnabled,
623                 boolean appInstallFilteringEnabled,
624                 boolean adRenderIdEnabled) {
625             mAdDataConversionStrategy =
626                     AdDataConversionStrategyFactory.getAdDataConversionStrategy(
627                             frequencyCapFilteringEnabled,
628                             appInstallFilteringEnabled,
629                             adRenderIdEnabled);
630         }
631 
632         /** Serialize {@link List<DBAdData>} to Json. */
633         @TypeConverter
634         @Nullable
toJson(@ullable List<DBAdData> adDataList)635         public String toJson(@Nullable List<DBAdData> adDataList) {
636             if (adDataList == null) {
637                 return null;
638             }
639             int toJsonTraceCookie = Tracing.beginAsyncSection(Tracing.DB_CUSTOM_AUDIENCE_TO_JSON);
640             try {
641                 JSONArray jsonArray = new JSONArray();
642                 for (DBAdData adData : adDataList) {
643                     jsonArray.put(mAdDataConversionStrategy.toJson(adData));
644                 }
645                 return jsonArray.toString();
646             } catch (JSONException jsonException) {
647                 throw new RuntimeException("Error serialize List<AdData>.", jsonException);
648             } finally {
649                 Tracing.endAsyncSection(Tracing.DB_CUSTOM_AUDIENCE_TO_JSON, toJsonTraceCookie);
650             }
651         }
652 
653         /** Deserialize {@link List<DBAdData>} from Json. */
654         @TypeConverter
655         @Nullable
fromJson(String json)656         public List<DBAdData> fromJson(String json) {
657             if (json == null) {
658                 return null;
659             }
660 
661             int fromJsonTraceCookie =
662                     Tracing.beginAsyncSection(Tracing.DB_CUSTOM_AUDIENCE_FROM_JSON);
663             try {
664                 JSONArray array = new JSONArray(json);
665                 List<DBAdData> result = new ArrayList<>();
666                 for (int i = 0; i < array.length(); i++) {
667                     JSONObject jsonObject = array.getJSONObject(i);
668                     result.add(mAdDataConversionStrategy.fromJson(jsonObject).build());
669                 }
670                 return result;
671             } catch (JSONException jsonException) {
672                 throw new RuntimeException("Error deserialize List<AdData>.", jsonException);
673             } finally {
674                 Tracing.endAsyncSection(Tracing.DB_CUSTOM_AUDIENCE_FROM_JSON, fromJsonTraceCookie);
675             }
676         }
677     }
678 }
679