| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.adservices.ondevicepersonalization; |
| |
| import android.adservices.ondevicepersonalization.aidl.IDataAccessService; |
| import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback; |
| import android.annotation.FlaggedApi; |
| import android.annotation.NonNull; |
| import android.annotation.WorkerThread; |
| import android.os.Bundle; |
| import android.os.Parcelable; |
| import android.os.RemoteException; |
| |
| import com.android.adservices.ondevicepersonalization.flags.Flags; |
| import com.android.ondevicepersonalization.internal.util.LoggerFactory; |
| import com.android.ondevicepersonalization.internal.util.OdpParceledListSlice; |
| |
| import java.time.Instant; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.ArrayBlockingQueue; |
| import java.util.concurrent.BlockingQueue; |
| |
| /** |
| * An interface to a read logs from REQUESTS and EVENTS |
| * |
| * Used as a Data Access Object for the REQUESTS and EVENTS table. |
| * |
| * @see IsolatedService#getLogReader(RequestToken) |
| * |
| */ |
| @FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED) |
| public class LogReader { |
| private static final String TAG = "LogReader"; |
| private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); |
| |
| @NonNull |
| private final IDataAccessService mDataAccessService; |
| |
| /** @hide */ |
| public LogReader(@NonNull IDataAccessService binder) { |
| mDataAccessService = Objects.requireNonNull(binder); |
| } |
| |
| |
| /** |
| * Retrieves a List of RequestLogRecords written by this IsolatedService within |
| * the specified time range. |
| */ |
| @WorkerThread |
| @NonNull |
| public List<RequestLogRecord> getRequests( |
| @NonNull Instant startTime, @NonNull Instant endTime) { |
| final long apiStartTimeMillis = System.currentTimeMillis(); |
| int responseCode = Constants.STATUS_SUCCESS; |
| long startTimeMillis = startTime.toEpochMilli(); |
| long endTimeMillis = endTime.toEpochMilli(); |
| if (endTimeMillis <= startTimeMillis) { |
| throw new IllegalArgumentException( |
| "endTimeMillis must be greater than startTimeMillis"); |
| } |
| if (startTimeMillis < 0) { |
| throw new IllegalArgumentException("startTimeMillis must be greater than 0"); |
| } |
| try { |
| Bundle params = new Bundle(); |
| params.putLongArray(Constants.EXTRA_LOOKUP_KEYS, |
| new long[]{startTimeMillis, endTimeMillis}); |
| OdpParceledListSlice<RequestLogRecord> result = |
| handleListLookupRequest(Constants.DATA_ACCESS_OP_GET_REQUESTS, params); |
| return result.getList(); |
| } catch (RuntimeException e) { |
| responseCode = Constants.STATUS_INTERNAL_ERROR; |
| throw e; |
| } finally { |
| logApiCallStats( |
| Constants.API_NAME_LOG_READER_GET_REQUESTS, |
| System.currentTimeMillis() - apiStartTimeMillis, |
| responseCode); |
| } |
| } |
| |
| /** |
| * Retrieves a List of EventLogRecord with its corresponding RequestLogRecord written by this |
| * IsolatedService within the specified time range. |
| */ |
| @WorkerThread |
| @NonNull |
| public List<EventLogRecord> getJoinedEvents( |
| @NonNull Instant startTime, @NonNull Instant endTime) { |
| final long apiStartTimeMillis = System.currentTimeMillis(); |
| int responseCode = Constants.STATUS_SUCCESS; |
| long startTimeMillis = startTime.toEpochMilli(); |
| long endTimeMillis = endTime.toEpochMilli(); |
| if (endTimeMillis <= startTimeMillis) { |
| throw new IllegalArgumentException( |
| "endTimeMillis must be greater than startTimeMillis"); |
| } |
| if (startTimeMillis < 0) { |
| throw new IllegalArgumentException("startTimeMillis must be greater than 0"); |
| } |
| try { |
| Bundle params = new Bundle(); |
| params.putLongArray(Constants.EXTRA_LOOKUP_KEYS, |
| new long[]{startTimeMillis, endTimeMillis}); |
| OdpParceledListSlice<EventLogRecord> result = |
| handleListLookupRequest(Constants.DATA_ACCESS_OP_GET_JOINED_EVENTS, params); |
| return result.getList(); |
| } catch (RuntimeException e) { |
| responseCode = Constants.STATUS_INTERNAL_ERROR; |
| throw e; |
| } finally { |
| logApiCallStats( |
| Constants.API_NAME_LOG_READER_GET_JOINED_EVENTS, |
| System.currentTimeMillis() - apiStartTimeMillis, |
| responseCode); |
| } |
| } |
| |
| private Bundle handleAsyncRequest(int op, Bundle params) { |
| try { |
| BlockingQueue<Bundle> asyncResult = new ArrayBlockingQueue<>(1); |
| mDataAccessService.onRequest( |
| op, |
| params, |
| new IDataAccessServiceCallback.Stub() { |
| @Override |
| public void onSuccess(@NonNull Bundle result) { |
| if (result != null) { |
| asyncResult.add(result); |
| } else { |
| asyncResult.add(Bundle.EMPTY); |
| } |
| } |
| |
| @Override |
| public void onError(int errorCode) { |
| asyncResult.add(Bundle.EMPTY); |
| } |
| }); |
| return asyncResult.take(); |
| } catch (InterruptedException | RemoteException e) { |
| sLogger.e(TAG + ": Failed to retrieve result", e); |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| private <T extends Parcelable> OdpParceledListSlice<T> handleListLookupRequest(int op, |
| Bundle params) { |
| Bundle result = handleAsyncRequest(op, params); |
| try { |
| OdpParceledListSlice<T> data = result.getParcelable( |
| Constants.EXTRA_RESULT, OdpParceledListSlice.class); |
| if (null == data) { |
| sLogger.e(TAG + ": No EXTRA_RESULT was present in bundle"); |
| throw new IllegalStateException("Bundle missing EXTRA_RESULT."); |
| } |
| return data; |
| } catch (ClassCastException e) { |
| throw new IllegalStateException("Failed to retrieve parceled list"); |
| } |
| } |
| |
| private void logApiCallStats(int apiName, long duration, int responseCode) { |
| try { |
| mDataAccessService.logApiCallStats(apiName, duration, responseCode); |
| } catch (Exception e) { |
| sLogger.d(e, TAG + ": failed to log metrics"); |
| } |
| } |
| } |