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