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 android.adservices.ondevicepersonalization;
18 
19 import android.adservices.ondevicepersonalization.aidl.IDataAccessService;
20 import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback;
21 import android.annotation.FlaggedApi;
22 import android.annotation.NonNull;
23 import android.annotation.WorkerThread;
24 import android.os.Bundle;
25 import android.os.Parcelable;
26 import android.os.RemoteException;
27 
28 import com.android.adservices.ondevicepersonalization.flags.Flags;
29 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
30 import com.android.ondevicepersonalization.internal.util.OdpParceledListSlice;
31 
32 import java.time.Instant;
33 import java.util.List;
34 import java.util.Objects;
35 import java.util.concurrent.ArrayBlockingQueue;
36 import java.util.concurrent.BlockingQueue;
37 
38 /**
39  * An interface to a read logs from REQUESTS and EVENTS
40  *
41  * Used as a Data Access Object for the REQUESTS and EVENTS table.
42  *
43  * @see IsolatedService#getLogReader(RequestToken)
44  *
45  */
46 @FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
47 public class LogReader {
48     private static final String TAG = "LogReader";
49     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
50 
51     @NonNull
52     private final IDataAccessService mDataAccessService;
53 
54     /** @hide */
LogReader(@onNull IDataAccessService binder)55     public LogReader(@NonNull IDataAccessService binder) {
56         mDataAccessService = Objects.requireNonNull(binder);
57     }
58 
59 
60     /**
61      * Retrieves a List of RequestLogRecords written by this IsolatedService within
62      * the specified time range.
63      */
64     @WorkerThread
65     @NonNull
getRequests( @onNull Instant startTime, @NonNull Instant endTime)66     public List<RequestLogRecord> getRequests(
67             @NonNull Instant startTime, @NonNull Instant endTime) {
68         final long apiStartTimeMillis = System.currentTimeMillis();
69         int responseCode = Constants.STATUS_SUCCESS;
70         long startTimeMillis = startTime.toEpochMilli();
71         long endTimeMillis = endTime.toEpochMilli();
72         if (endTimeMillis <= startTimeMillis) {
73             throw new IllegalArgumentException(
74                     "endTimeMillis must be greater than startTimeMillis");
75         }
76         if (startTimeMillis < 0) {
77             throw new IllegalArgumentException("startTimeMillis must be greater than 0");
78         }
79         try {
80             Bundle params = new Bundle();
81             params.putLongArray(Constants.EXTRA_LOOKUP_KEYS,
82                     new long[]{startTimeMillis, endTimeMillis});
83             OdpParceledListSlice<RequestLogRecord> result =
84                     handleListLookupRequest(Constants.DATA_ACCESS_OP_GET_REQUESTS, params);
85             return result.getList();
86         } catch (RuntimeException e) {
87             responseCode = Constants.STATUS_INTERNAL_ERROR;
88             throw e;
89         } finally {
90             logApiCallStats(
91                     Constants.API_NAME_LOG_READER_GET_REQUESTS,
92                     System.currentTimeMillis() - apiStartTimeMillis,
93                     responseCode);
94         }
95     }
96 
97     /**
98      * Retrieves a List of EventLogRecord with its corresponding RequestLogRecord written by this
99      * IsolatedService within the specified time range.
100      */
101     @WorkerThread
102     @NonNull
getJoinedEvents( @onNull Instant startTime, @NonNull Instant endTime)103     public List<EventLogRecord> getJoinedEvents(
104             @NonNull Instant startTime, @NonNull Instant endTime) {
105         final long apiStartTimeMillis = System.currentTimeMillis();
106         int responseCode = Constants.STATUS_SUCCESS;
107         long startTimeMillis = startTime.toEpochMilli();
108         long endTimeMillis = endTime.toEpochMilli();
109         if (endTimeMillis <= startTimeMillis) {
110             throw new IllegalArgumentException(
111                     "endTimeMillis must be greater than startTimeMillis");
112         }
113         if (startTimeMillis < 0) {
114             throw new IllegalArgumentException("startTimeMillis must be greater than 0");
115         }
116         try {
117             Bundle params = new Bundle();
118             params.putLongArray(Constants.EXTRA_LOOKUP_KEYS,
119                     new long[]{startTimeMillis, endTimeMillis});
120             OdpParceledListSlice<EventLogRecord> result =
121                     handleListLookupRequest(Constants.DATA_ACCESS_OP_GET_JOINED_EVENTS, params);
122             return result.getList();
123         } catch (RuntimeException e) {
124             responseCode = Constants.STATUS_INTERNAL_ERROR;
125             throw e;
126         } finally {
127             logApiCallStats(
128                     Constants.API_NAME_LOG_READER_GET_JOINED_EVENTS,
129                     System.currentTimeMillis() - apiStartTimeMillis,
130                     responseCode);
131         }
132     }
133 
handleAsyncRequest(int op, Bundle params)134     private Bundle handleAsyncRequest(int op, Bundle params) {
135         try {
136             BlockingQueue<Bundle> asyncResult = new ArrayBlockingQueue<>(1);
137             mDataAccessService.onRequest(
138                     op,
139                     params,
140                     new IDataAccessServiceCallback.Stub() {
141                         @Override
142                         public void onSuccess(@NonNull Bundle result) {
143                             if (result != null) {
144                                 asyncResult.add(result);
145                             } else {
146                                 asyncResult.add(Bundle.EMPTY);
147                             }
148                         }
149 
150                         @Override
151                         public void onError(int errorCode) {
152                             asyncResult.add(Bundle.EMPTY);
153                         }
154                     });
155             return asyncResult.take();
156         } catch (InterruptedException | RemoteException e) {
157             sLogger.e(TAG + ": Failed to retrieve result", e);
158             throw new IllegalStateException(e);
159         }
160     }
161 
handleListLookupRequest(int op, Bundle params)162     private <T extends Parcelable> OdpParceledListSlice<T> handleListLookupRequest(int op,
163             Bundle params) {
164         Bundle result = handleAsyncRequest(op, params);
165         try {
166             OdpParceledListSlice<T> data = result.getParcelable(
167                     Constants.EXTRA_RESULT, OdpParceledListSlice.class);
168             if (null == data) {
169                 sLogger.e(TAG + ": No EXTRA_RESULT was present in bundle");
170                 throw new IllegalStateException("Bundle missing EXTRA_RESULT.");
171             }
172             return data;
173         } catch (ClassCastException e) {
174             throw new IllegalStateException("Failed to retrieve parceled list");
175         }
176     }
177 
logApiCallStats(int apiName, long duration, int responseCode)178     private void logApiCallStats(int apiName, long duration, int responseCode) {
179         try {
180             mDataAccessService.logApiCallStats(apiName, duration, responseCode);
181         } catch (Exception e) {
182             sLogger.d(e, TAG + ": failed to log metrics");
183         }
184     }
185 }
186