1 /* 2 * Copyright (C) 2024 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.display; 18 19 import android.adservices.ondevicepersonalization.Constants; 20 import android.adservices.ondevicepersonalization.EventInputParcel; 21 import android.adservices.ondevicepersonalization.EventLogRecord; 22 import android.adservices.ondevicepersonalization.EventOutputParcel; 23 import android.adservices.ondevicepersonalization.RequestLogRecord; 24 import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.os.Bundle; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.odp.module.common.Clock; 33 import com.android.odp.module.common.MonotonicClock; 34 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 35 import com.android.ondevicepersonalization.services.Flags; 36 import com.android.ondevicepersonalization.services.FlagsFactory; 37 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 38 import com.android.ondevicepersonalization.services.data.DataAccessPermission; 39 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl; 40 import com.android.ondevicepersonalization.services.data.events.Event; 41 import com.android.ondevicepersonalization.services.data.events.EventUrlPayload; 42 import com.android.ondevicepersonalization.services.data.events.EventsDao; 43 import com.android.ondevicepersonalization.services.inference.IsolatedModelServiceProvider; 44 import com.android.ondevicepersonalization.services.policyengine.UserDataAccessor; 45 import com.android.ondevicepersonalization.services.serviceflow.ServiceFlow; 46 import com.android.ondevicepersonalization.services.util.OnDevicePersonalizationFlatbufferUtils; 47 import com.android.ondevicepersonalization.services.util.StatsUtils; 48 49 import com.google.common.util.concurrent.FluentFuture; 50 import com.google.common.util.concurrent.FutureCallback; 51 import com.google.common.util.concurrent.Futures; 52 import com.google.common.util.concurrent.ListenableFuture; 53 import com.google.common.util.concurrent.ListeningExecutorService; 54 import com.google.common.util.concurrent.ListeningScheduledExecutorService; 55 56 import java.util.Objects; 57 import java.util.concurrent.TimeUnit; 58 59 /** Implementation of common web view client logic. */ 60 public class WebViewFlow implements ServiceFlow<EventOutputParcel> { 61 62 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 63 64 private static final String TAG = WebViewFlow.class.getSimpleName(); 65 66 public static final String TASK_NAME = "ComputeEventMetrics"; 67 68 private static final Object sDedupLock = new Object(); 69 70 @NonNull 71 private final Context mContext; 72 @NonNull 73 private final ComponentName mService; 74 @Nullable 75 private final RequestLogRecord mLogRecord; 76 @NonNull 77 private final Injector mInjector; 78 @NonNull 79 private IsolatedModelServiceProvider mModelServiceProvider; 80 long mQueryId; 81 private final EventUrlPayload mPayload; 82 private long mStartServiceTimeMillis; 83 private final FutureCallback<EventOutputParcel> mCallback; 84 WebViewFlow(Context context, ComponentName service, long queryId, RequestLogRecord logRecord, FutureCallback<EventOutputParcel> callback, EventUrlPayload payLoad)85 public WebViewFlow(Context context, ComponentName service, long queryId, 86 RequestLogRecord logRecord, FutureCallback<EventOutputParcel> callback, 87 EventUrlPayload payLoad) { 88 mContext = context; 89 mService = service; 90 mQueryId = queryId; 91 mLogRecord = logRecord; 92 mCallback = callback; 93 mPayload = payLoad; 94 mInjector = new Injector(); 95 } 96 97 @VisibleForTesting 98 public static class Injector { getExecutor()99 ListeningExecutorService getExecutor() { 100 return OnDevicePersonalizationExecutors.getBackgroundExecutor(); 101 } 102 getClock()103 Clock getClock() { 104 return MonotonicClock.getInstance(); 105 } 106 getFlags()107 Flags getFlags() { 108 return FlagsFactory.getFlags(); 109 } 110 getScheduledExecutor()111 ListeningScheduledExecutorService getScheduledExecutor() { 112 return OnDevicePersonalizationExecutors.getScheduledExecutor(); 113 } 114 } 115 116 @Override isServiceFlowReady()117 public boolean isServiceFlowReady() { 118 try { 119 mStartServiceTimeMillis = mInjector.getClock().elapsedRealtime(); 120 121 Objects.requireNonNull(mPayload); 122 123 return true; 124 } catch (Exception e) { 125 sLogger.d(TAG + "isServiceFlowReady() call failed: " + e.getMessage()); 126 mCallback.onFailure(e); 127 return false; 128 } 129 } 130 131 @Override getService()132 public ComponentName getService() { 133 return mService; 134 } 135 136 @Override getServiceParams()137 public Bundle getServiceParams() { 138 Bundle serviceParams = new Bundle(); 139 140 serviceParams.putBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER, 141 new DataAccessServiceImpl( 142 mService, mContext, 143 /* localDataPermission */ DataAccessPermission.READ_WRITE, 144 /* eventDataPermission */ DataAccessPermission.READ_ONLY)); 145 serviceParams.putParcelable(Constants.EXTRA_INPUT, 146 new EventInputParcel.Builder() 147 .setParameters(mPayload.getEventParams()) 148 .setRequestLogRecord(mLogRecord) 149 .build()); 150 serviceParams.putParcelable(Constants.EXTRA_USER_DATA, 151 new UserDataAccessor().getUserData()); 152 153 mModelServiceProvider = new IsolatedModelServiceProvider(); 154 IIsolatedModelService modelService = mModelServiceProvider.getModelService(mContext); 155 serviceParams.putBinder(Constants.EXTRA_MODEL_SERVICE_BINDER, modelService.asBinder()); 156 157 return serviceParams; 158 } 159 160 @Override uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture)161 public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) { 162 var unused = FluentFuture.from(runServiceFuture) 163 .transform( 164 result -> { 165 StatsUtils.writeServiceRequestMetrics( 166 Constants.API_NAME_SERVICE_ON_EVENT, 167 result, mInjector.getClock(), 168 Constants.STATUS_SUCCESS, 169 mStartServiceTimeMillis); 170 return null; 171 }, 172 mInjector.getExecutor()) 173 .catchingAsync( 174 Exception.class, 175 e -> { 176 StatsUtils.writeServiceRequestMetrics( 177 Constants.API_NAME_SERVICE_ON_EVENT, 178 /* result= */ null, mInjector.getClock(), 179 Constants.STATUS_INTERNAL_ERROR, 180 mStartServiceTimeMillis); 181 return Futures.immediateFailedFuture(e); 182 }, 183 mInjector.getExecutor()); 184 } 185 186 @Override getServiceFlowResultFuture( ListenableFuture<Bundle> runServiceFuture)187 public ListenableFuture<EventOutputParcel> getServiceFlowResultFuture( 188 ListenableFuture<Bundle> runServiceFuture) { 189 return FluentFuture.from(runServiceFuture) 190 .transform( 191 result -> result.getParcelable( 192 Constants.EXTRA_RESULT, EventOutputParcel.class), 193 mInjector.getExecutor()) 194 .transform( 195 result -> { 196 var unused = writeEvent(result); 197 return result; 198 }, 199 mInjector.getExecutor()) 200 .withTimeout( 201 mInjector.getFlags().getIsolatedServiceDeadlineSeconds(), 202 TimeUnit.SECONDS, 203 mInjector.getScheduledExecutor()); 204 } 205 206 @Override returnResultThroughCallback( ListenableFuture<EventOutputParcel> serviceFlowResultFuture)207 public void returnResultThroughCallback( 208 ListenableFuture<EventOutputParcel> serviceFlowResultFuture) { 209 Futures.addCallback( 210 serviceFlowResultFuture, 211 new FutureCallback<>() { 212 @Override 213 public void onSuccess(EventOutputParcel result) { 214 mCallback.onSuccess(result); 215 } 216 217 @Override 218 public void onFailure(Throwable t) { 219 mCallback.onFailure(t); 220 } 221 }, 222 mInjector.getExecutor()); 223 } 224 225 @Override cleanUpServiceParams()226 public void cleanUpServiceParams() { 227 mModelServiceProvider.unBindFromModelService(); 228 } 229 230 // TO-DO: Add errors and propagate back to caller through callback. writeEvent(EventOutputParcel result)231 private ListenableFuture<Void> writeEvent(EventOutputParcel result) { 232 try { 233 sLogger.d(TAG + ": writeEvent() called. EventOutputParcel: " + result.toString()); 234 if (result == null || result.getEventLogRecord() == null 235 || mLogRecord == null || mLogRecord.getRows() == null) { 236 sLogger.d(TAG + "no EventLogRecord or RequestLogRecord"); 237 return Futures.immediateFuture(null); 238 } 239 EventLogRecord eventData = result.getEventLogRecord(); 240 int rowCount = mLogRecord.getRows().size(); 241 if (eventData.getType() <= 0 || eventData.getRowIndex() < 0 242 || eventData.getRowIndex() >= rowCount) { 243 sLogger.w(TAG + ": rowOffset out of range"); 244 return Futures.immediateFuture(null); 245 } 246 247 byte[] data = OnDevicePersonalizationFlatbufferUtils.createEventData( 248 eventData.getData()); 249 Event event = new Event.Builder() 250 .setType(eventData.getType()) 251 .setQueryId(mQueryId) 252 .setService(mService) 253 .setTimeMillis(mInjector.getClock().currentTimeMillis()) 254 .setRowIndex(eventData.getRowIndex()) 255 .setEventData(data) 256 .build(); 257 EventsDao dao = EventsDao.getInstance(mContext); 258 synchronized (sDedupLock) { 259 // Do not insert duplicate event. 260 // TODO(b/340264727): Enforce this constraint in DB. 261 if (!dao.hasEvent( 262 mQueryId, eventData.getType(), eventData.getRowIndex(), mService)) { 263 if (-1 == dao.insertEvent(event)) { 264 sLogger.e(TAG + ": Failed to insert event: " + event); 265 } 266 } 267 } 268 return Futures.immediateFuture(null); 269 } catch (Exception e) { 270 sLogger.e(TAG + ": writeEvent() failed", e); 271 return Futures.immediateFailedFuture(e); 272 } 273 } 274 } 275