• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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