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