1 /*
2  * Copyright (C) 2023 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.ondevicepersonalization.services.util;
18 
19 import android.adservices.ondevicepersonalization.EventLogRecord;
20 import android.adservices.ondevicepersonalization.RequestLogRecord;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.ComponentName;
24 import android.content.ContentValues;
25 import android.content.Context;
26 
27 import com.android.odp.module.common.PackageUtils;
28 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
29 import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication;
30 import com.android.ondevicepersonalization.services.data.DbUtils;
31 import com.android.ondevicepersonalization.services.data.events.Event;
32 import com.android.ondevicepersonalization.services.data.events.EventsDao;
33 import com.android.ondevicepersonalization.services.data.events.Query;
34 
35 import com.google.common.util.concurrent.Futures;
36 import com.google.common.util.concurrent.ListenableFuture;
37 
38 import java.util.ArrayList;
39 import java.util.List;
40 
41 /** Utilities for logging */
42 public class LogUtils {
43     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
44     private static final String TAG = "LogUtils";
45 
46     /** Writes the provided records to the REQUESTS and EVENTS tables. */
writeLogRecords( @onNull Context context, @NonNull String appPackageName, @NonNull ComponentName service, @Nullable RequestLogRecord requestLogRecord, @NonNull List<EventLogRecord> eventLogRecords)47     public static ListenableFuture<Long> writeLogRecords(
48             @NonNull Context context,
49             @NonNull String appPackageName,
50             @NonNull ComponentName service,
51             @Nullable RequestLogRecord requestLogRecord,
52             @NonNull List<EventLogRecord> eventLogRecords) {
53         sLogger.d(TAG + ": writeLogRecords() started.");
54         try {
55             String serviceName = DbUtils.toTableValue(service);
56             String certDigest =
57                     PackageUtils.getCertDigest(
58                             OnDevicePersonalizationApplication.getAppContext(),
59                             service.getPackageName());
60             EventsDao eventsDao = EventsDao.getInstance(context);
61             // Insert query
62             long queryId = -1;
63             if (requestLogRecord != null) {
64                 List<ContentValues> rows = requestLogRecord.getRows();
65                 if (rows.isEmpty()) {
66                     rows = List.of(new ContentValues());
67                 }
68                 byte[] queryData = OnDevicePersonalizationFlatbufferUtils.createQueryData(
69                         serviceName, certDigest, rows);
70                 Query query = new Query.Builder(
71                         System.currentTimeMillis(),
72                         appPackageName,
73                         service,
74                         certDigest,
75                         queryData).build();
76                 queryId = eventsDao.insertQuery(query);
77                 if (queryId == -1) {
78                     return Futures.immediateFailedFuture(
79                             new RuntimeException("Failed to log query."));
80                 }
81             }
82 
83             // Insert events
84             List<Event> events = new ArrayList<>();
85             for (EventLogRecord eventLogRecord : eventLogRecords) {
86                 RequestLogRecord parent;
87                 long parentRequestId;
88                 if (eventLogRecord.getRequestLogRecord() != null) {
89                     parent = eventLogRecord.getRequestLogRecord();
90                     parentRequestId = parent.getRequestId();
91                 } else {
92                     parent = requestLogRecord;
93                     parentRequestId = queryId;
94                 }
95                 // Verify requestLogRecord exists and has the corresponding rowIndex
96                 if (parent == null || parentRequestId <= 0
97                         || eventLogRecord.getRowIndex() >= parent.getRows().size()) {
98                     continue;
99                 }
100                 // Make sure query exists for package in QUERY table and
101                 // rowIndex <= written row count.
102                 Query queryRow = eventsDao.readSingleQueryRow(parentRequestId, service);
103                 if (queryRow == null || eventLogRecord.getRowIndex()
104                         >= OnDevicePersonalizationFlatbufferUtils
105                                 .getContentValuesLengthFromQueryData(
106                                         queryRow.getQueryData())) {
107                     continue;
108                 }
109                 Event event = new Event.Builder()
110                         .setEventData(OnDevicePersonalizationFlatbufferUtils.createEventData(
111                                 eventLogRecord.getData()))
112                         .setQueryId(parentRequestId)
113                         .setRowIndex(eventLogRecord.getRowIndex())
114                         .setService(service)
115                         .setTimeMillis(System.currentTimeMillis())
116                         .setType(eventLogRecord.getType())
117                         .build();
118                 events.add(event);
119             }
120             if (!eventsDao.insertEvents(events)) {
121                 return Futures.immediateFailedFuture(
122                         new RuntimeException("Failed to log events."));
123             }
124 
125             return Futures.immediateFuture(queryId);
126         } catch (Exception e) {
127             return Futures.immediateFailedFuture(e);
128         }
129     }
130 
LogUtils()131     private LogUtils() {}
132 }
133