1 /*
2  * Copyright (C) 2024 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.service.kanon;
18 
19 import android.annotation.NonNull;
20 
21 import com.android.adservices.data.kanon.DBKAnonMessage;
22 import com.android.adservices.data.kanon.KAnonMessageConstants;
23 import com.android.adservices.data.kanon.KAnonMessageDao;
24 import com.android.adservices.service.Flags;
25 
26 import java.time.Clock;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Objects;
30 import java.util.stream.Collectors;
31 
32 /** Class to manage fetching and persisting of {@link KAnonMessageEntity}. */
33 public class KAnonMessageManager {
34 
35     @NonNull private final KAnonMessageDao mKAnonMessageDao;
36     @NonNull private final Flags mFlags;
37     @NonNull private final Clock mClock;
38 
KAnonMessageManager( @onNull KAnonMessageDao kAnonMessageDao, @NonNull Flags flags, @NonNull Clock clock)39     public KAnonMessageManager(
40             @NonNull KAnonMessageDao kAnonMessageDao, @NonNull Flags flags, @NonNull Clock clock) {
41         Objects.requireNonNull(kAnonMessageDao);
42         Objects.requireNonNull(flags);
43         Objects.requireNonNull(clock);
44 
45         mKAnonMessageDao = kAnonMessageDao;
46         mFlags = flags;
47         mClock = clock;
48     }
49 
50     /**
51      * This method is used to persist NEW {@link KAnonMessageEntity} in the {@link DBKAnonMessage}
52      * table.
53      */
persistNewAnonMessageEntities( List<KAnonMessageEntity> kAnonMessageEntityList)54     public List<KAnonMessageEntity> persistNewAnonMessageEntities(
55             List<KAnonMessageEntity> kAnonMessageEntityList) {
56         List<DBKAnonMessage> dbkAnonMessages =
57                 kAnonMessageEntityList.stream()
58                         .map(this::parseKAnonMessageEntityToNewDBKAnonMessage)
59                         .collect(Collectors.toList());
60         long[] ids = mKAnonMessageDao.insertAllKAnonMessages(dbkAnonMessages);
61         List<KAnonMessageEntity> newKAnonMessageEntities = new ArrayList<>();
62         for (int i = 0; i < ids.length; i++) {
63             KAnonMessageEntity currentEntity = kAnonMessageEntityList.get(i);
64             newKAnonMessageEntities.add(
65                     KAnonMessageEntity.builder()
66                             .setAdSelectionId(currentEntity.getAdSelectionId())
67                             .setMessageId(ids[i])
68                             .setHashSet(currentEntity.getHashSet())
69                             .setStatus(currentEntity.getStatus())
70                             .build());
71         }
72         return newKAnonMessageEntities;
73     }
74 
75     /**
76      * This method fetches a list of {@link DBKAnonMessage} from the database, parses it and returns
77      * a list of {@link KAnonMessageEntity}.
78      *
79      * @param numberOfMessages number of messages to be fetched.
80      * @param status status of the messages that needed to be fetched
81      */
fetchNKAnonMessagesWithStatus( int numberOfMessages, @KAnonMessageConstants.MessageStatus int status)82     public List<KAnonMessageEntity> fetchNKAnonMessagesWithStatus(
83             int numberOfMessages, @KAnonMessageConstants.MessageStatus int status) {
84         return mKAnonMessageDao.getNLatestKAnonMessagesWithStatus(numberOfMessages, status).stream()
85                 .map(this::parseDBKAnonMessageToKAnonMessageEntity)
86                 .collect(Collectors.toList());
87     }
88 
89     /**
90      * @param hashSetToSearch Fetches the {@link DBKAnonMessage} matching the searchText and parses
91      *     it to a {@link KAnonMessageEntity}
92      */
fetchKAnonMessageEntityWithMessage(String hashSetToSearch)93     public List<KAnonMessageEntity> fetchKAnonMessageEntityWithMessage(String hashSetToSearch) {
94         return mKAnonMessageDao.getKAnonMessagesWithMessage(hashSetToSearch).stream()
95                 .map(this::parseDBKAnonMessageToKAnonMessageEntity)
96                 .collect(Collectors.toList());
97     }
98 
99     /**
100      * Returns the number of messages with NOT_PROCESSED {@link KAnonMessageConstants.MessageStatus}
101      * status in the database.
102      */
getNumberOfUnprocessedMessagesInDB()103     public int getNumberOfUnprocessedMessagesInDB() {
104         return mKAnonMessageDao.getNumberOfMessagesWithStatus(
105                 KAnonMessageConstants.MessageStatus.NOT_PROCESSED);
106     }
107 
108     /** Updates the status of messages in the table. */
updateMessagesStatus( List<KAnonMessageEntity> messageEntities, @KAnonMessageEntity.KanonMessageEntityStatus int status)109     public void updateMessagesStatus(
110             List<KAnonMessageEntity> messageEntities,
111             @KAnonMessageEntity.KanonMessageEntityStatus int status) {
112         List<Long> idsToUpdate =
113                 messageEntities.stream()
114                         .map(KAnonMessageEntity::getMessageId)
115                         .collect(Collectors.toList());
116 
117         mKAnonMessageDao.updateMessagesStatus(
118                 idsToUpdate, KAnonMessageConstants.fromKAnonMessageEntityStatus(status));
119     }
120 
parseKAnonMessageEntityToNewDBKAnonMessage( KAnonMessageEntity kAnonMessageEntity)121     private DBKAnonMessage parseKAnonMessageEntityToNewDBKAnonMessage(
122             KAnonMessageEntity kAnonMessageEntity) {
123         if (kAnonMessageEntity == null) {
124             return null;
125         }
126         return DBKAnonMessage.builder()
127                 .setAdSelectionId(kAnonMessageEntity.getAdSelectionId())
128                 .setKanonHashSet(kAnonMessageEntity.getHashSet())
129                 .setStatus(
130                         KAnonMessageConstants.fromKAnonMessageEntityStatus(
131                                 kAnonMessageEntity.getStatus()))
132                 // TODO(b/325606196): stable kanon flags.
133                 .setExpiryInstant(
134                         mClock.instant().plusSeconds(mFlags.getFledgeKAnonMessageTtlSeconds()))
135                 .setCreatedAt(mClock.instant())
136                 .build();
137     }
138 
parseDBKAnonMessageToKAnonMessageEntity( DBKAnonMessage dbkAnonMessage)139     private KAnonMessageEntity parseDBKAnonMessageToKAnonMessageEntity(
140             DBKAnonMessage dbkAnonMessage) {
141         if (dbkAnonMessage == null) {
142             return null;
143         }
144         return KAnonMessageEntity.builder()
145                 .setMessageId(dbkAnonMessage.getMessageId())
146                 .setHashSet(dbkAnonMessage.getKanonHashSet())
147                 .setAdSelectionId(dbkAnonMessage.getAdSelectionId())
148                 .setStatus(
149                         KAnonMessageConstants.toKAnonMessageEntityStatus(
150                                 dbkAnonMessage.getStatus()))
151                 .setCorrespondingClientParametersExpiryInstant(
152                         dbkAnonMessage.getCorrespondingClientParametersExpiryInstant())
153                 .build();
154     }
155 }
156