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 com.android.ondevicepersonalization.services.request;
18 
19 import android.adservices.ondevicepersonalization.CalleeMetadata;
20 import android.adservices.ondevicepersonalization.Constants;
21 import android.adservices.ondevicepersonalization.RenderInputParcel;
22 import android.adservices.ondevicepersonalization.RenderOutputParcel;
23 import android.adservices.ondevicepersonalization.RenderingConfig;
24 import android.adservices.ondevicepersonalization.RequestLogRecord;
25 import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
26 import android.annotation.NonNull;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.os.Bundle;
30 import android.os.IBinder;
31 import android.os.RemoteException;
32 import android.os.SystemClock;
33 import android.view.SurfaceControlViewHost.SurfacePackage;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.odp.module.common.Clock;
37 import com.android.odp.module.common.MonotonicClock;
38 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
39 import com.android.ondevicepersonalization.services.Flags;
40 import com.android.ondevicepersonalization.services.FlagsFactory;
41 import com.android.ondevicepersonalization.services.OdpServiceException;
42 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
43 import com.android.ondevicepersonalization.services.data.DataAccessPermission;
44 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl;
45 import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
46 import com.android.ondevicepersonalization.services.display.DisplayHelper;
47 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
48 import com.android.ondevicepersonalization.services.serviceflow.ServiceFlow;
49 import com.android.ondevicepersonalization.services.util.CryptUtils;
50 import com.android.ondevicepersonalization.services.util.DebugUtils;
51 import com.android.ondevicepersonalization.services.util.StatsUtils;
52 
53 import com.google.common.util.concurrent.FluentFuture;
54 import com.google.common.util.concurrent.FutureCallback;
55 import com.google.common.util.concurrent.Futures;
56 import com.google.common.util.concurrent.ListenableFuture;
57 import com.google.common.util.concurrent.ListeningExecutorService;
58 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
59 
60 import java.util.Objects;
61 import java.util.concurrent.TimeUnit;
62 
63 /**
64  * Handles a surface package request from an app or SDK.
65  */
66 public class RenderFlow implements ServiceFlow<SurfacePackage> {
67     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
68     private static final String TAG = "RenderFlow";
69 
70     @VisibleForTesting
71     static class Injector {
getExecutor()72         ListeningExecutorService getExecutor() {
73             return OnDevicePersonalizationExecutors.getBackgroundExecutor();
74         }
75 
decryptToken(String slotResultToken)76         SlotWrapper decryptToken(String slotResultToken) throws Exception {
77             return (SlotWrapper) CryptUtils.decrypt(slotResultToken);
78         }
79 
getClock()80         Clock getClock() {
81             return MonotonicClock.getInstance();
82         }
83 
getFlags()84         Flags getFlags() {
85             return FlagsFactory.getFlags();
86         }
87 
getScheduledExecutor()88         ListeningScheduledExecutorService getScheduledExecutor() {
89             return OnDevicePersonalizationExecutors.getScheduledExecutor();
90         }
91     }
92 
93     @NonNull
94     private final String mSlotResultToken;
95     @NonNull
96     private final IBinder mHostToken;
97     @NonNull private final int mDisplayId;
98     @NonNull private final int mWidth;
99     @NonNull private final int mHeight;
100     @NonNull
101     private final IRequestSurfacePackageCallback mCallback;
102     @NonNull
103     private final Context mContext;
104     private final long mStartTimeMillis;
105     private final long mServiceEntryTimeMillis;
106     @NonNull
107     private final Injector mInjector;
108     @NonNull
109     private final DisplayHelper mDisplayHelper;
110     @NonNull
111     private ComponentName mService;
112     private SlotWrapper mSlotWrapper;
113     private long mStartServiceTimeMillis;
114 
RenderFlow( @onNull String slotResultToken, @NonNull IBinder hostToken, int displayId, int width, int height, @NonNull IRequestSurfacePackageCallback callback, @NonNull Context context, long startTimeMillis, long serviceEntryTimeMillis)115     public RenderFlow(
116             @NonNull String slotResultToken,
117             @NonNull IBinder hostToken,
118             int displayId,
119             int width,
120             int height,
121             @NonNull IRequestSurfacePackageCallback callback,
122             @NonNull Context context,
123             long startTimeMillis,
124             long serviceEntryTimeMillis) {
125         this(slotResultToken, hostToken, displayId, width, height,
126                 callback, context, startTimeMillis, serviceEntryTimeMillis,
127                 new Injector(),
128                 new DisplayHelper(context));
129     }
130 
131     @VisibleForTesting
RenderFlow( @onNull String slotResultToken, @NonNull IBinder hostToken, int displayId, int width, int height, @NonNull IRequestSurfacePackageCallback callback, @NonNull Context context, long startTimeMillis, long serviceEntryTimeMillis, @NonNull Injector injector, @NonNull DisplayHelper displayHelper)132     RenderFlow(
133             @NonNull String slotResultToken,
134             @NonNull IBinder hostToken,
135             int displayId,
136             int width,
137             int height,
138             @NonNull IRequestSurfacePackageCallback callback,
139             @NonNull Context context,
140             long startTimeMillis,
141             long serviceEntryTimeMillis,
142             @NonNull Injector injector,
143             @NonNull DisplayHelper displayHelper) {
144         sLogger.d(TAG + ": RenderFlow created.");
145         mSlotResultToken = Objects.requireNonNull(slotResultToken);
146         mHostToken = Objects.requireNonNull(hostToken);
147         mDisplayId = displayId;
148         mWidth = width;
149         mHeight = height;
150         mCallback = Objects.requireNonNull(callback);
151         mStartTimeMillis = startTimeMillis;
152         mServiceEntryTimeMillis = serviceEntryTimeMillis;
153         mInjector = Objects.requireNonNull(injector);
154         mContext = Objects.requireNonNull(context);
155         mDisplayHelper = Objects.requireNonNull(displayHelper);
156     }
157 
158     @Override
isServiceFlowReady()159     public boolean isServiceFlowReady() {
160         mStartServiceTimeMillis = mInjector.getClock().elapsedRealtime();
161 
162         try {
163             if (!UserPrivacyStatus.getInstance().isProtectedAudienceEnabled()) {
164                 sLogger.d(TAG + ": User control is not given for targeting.");
165                 sendErrorResult(Constants.STATUS_PERSONALIZATION_DISABLED, 0);
166                 return false;
167             }
168 
169             mSlotWrapper = Objects.requireNonNull(
170                     mInjector.decryptToken(mSlotResultToken));
171             String servicePackageName = Objects.requireNonNull(
172                     mSlotWrapper.getServicePackageName());
173             String serviceClassName = Objects.requireNonNull(
174                     AppManifestConfigHelper.getServiceNameFromOdpSettings(
175                             mContext, servicePackageName));
176             mService = ComponentName.createRelative(servicePackageName, serviceClassName);
177         } catch (Exception e) {
178             sendErrorResult(Constants.STATUS_INTERNAL_ERROR, 0);
179             return false;
180         }
181         return true;
182     }
183 
184     @Override
getService()185     public ComponentName getService() {
186         return mService;
187     }
188 
189     @Override
getServiceParams()190     public Bundle getServiceParams() {
191         RenderingConfig renderingConfig =
192                 Objects.requireNonNull(mSlotWrapper.getRenderingConfig());
193 
194         Bundle serviceParams = new Bundle();
195 
196         serviceParams.putParcelable(
197                 Constants.EXTRA_INPUT, new RenderInputParcel.Builder()
198                         .setHeight(mHeight)
199                         .setWidth(mWidth)
200                         .setRenderingConfig(renderingConfig)
201                         .build());
202         serviceParams.putBinder(
203                 Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER, new DataAccessServiceImpl(
204                         mService, mContext, /* includeLocalData */ DataAccessPermission.DENIED,
205                         /* includeEventData */ DataAccessPermission.DENIED));
206 
207         return serviceParams;
208     }
209 
210     @Override
uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture)211     public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) {
212         var unused = FluentFuture.from(runServiceFuture)
213                 .transform(
214                         val -> {
215                             StatsUtils.writeServiceRequestMetrics(
216                                     Constants.API_NAME_SERVICE_ON_RENDER,
217                                     val, mInjector.getClock(),
218                                     Constants.STATUS_SUCCESS, mStartServiceTimeMillis);
219                             return val;
220                         },
221                         mInjector.getExecutor()
222                 )
223                 .catchingAsync(
224                         Exception.class,
225                         e -> {
226                             StatsUtils.writeServiceRequestMetrics(
227                                     Constants.API_NAME_SERVICE_ON_RENDER, /* result= */ null,
228                                     mInjector.getClock(),
229                                     Constants.STATUS_INTERNAL_ERROR, mStartServiceTimeMillis);
230                             return Futures.immediateFailedFuture(e);
231                         },
232                         mInjector.getExecutor()
233                 );
234     }
235 
236     @Override
getServiceFlowResultFuture( ListenableFuture<Bundle> runServiceFuture)237     public ListenableFuture<SurfacePackage> getServiceFlowResultFuture(
238             ListenableFuture<Bundle> runServiceFuture) {
239         RequestLogRecord logRecord = mSlotWrapper.getLogRecord();
240         long queryId = mSlotWrapper.getQueryId();
241 
242         return FluentFuture.from(runServiceFuture)
243                 .transform(
244                         result ->
245                                 result.getParcelable(
246                                         Constants.EXTRA_RESULT, RenderOutputParcel.class),
247                         mInjector.getExecutor())
248                 .transform(
249                         result -> mDisplayHelper.generateHtml(
250                                 result, mService),
251                         mInjector.getExecutor())
252                 .transformAsync(
253                         result -> mDisplayHelper.displayHtml(
254                                 result,
255                                 logRecord,
256                                 queryId,
257                                 mService,
258                                 mHostToken,
259                                 mDisplayId,
260                                 mWidth,
261                                 mHeight),
262                         mInjector.getExecutor())
263                 .withTimeout(
264                         mInjector.getFlags().getIsolatedServiceDeadlineSeconds(),
265                         TimeUnit.SECONDS,
266                         mInjector.getScheduledExecutor()
267                 );
268     }
269 
270     @Override
returnResultThroughCallback( ListenableFuture<SurfacePackage> serviceFlowResultFuture)271     public  void returnResultThroughCallback(
272             ListenableFuture<SurfacePackage> serviceFlowResultFuture) {
273         Futures.addCallback(
274                 serviceFlowResultFuture,
275                 new FutureCallback<>() {
276                     @Override
277                     public void onSuccess(SurfacePackage surfacePackage) {
278                         sendDisplayResult(surfacePackage);
279                     }
280 
281                     @Override
282                     public void onFailure(Throwable t) {
283                         sLogger.w(TAG + ": Request failed.", t);
284                         if (t instanceof OdpServiceException) {
285                             OdpServiceException e = (OdpServiceException) t;
286                             sendErrorResult(
287                                     e.getErrorCode(),
288                                     DebugUtils.getIsolatedServiceExceptionCode(
289                                             mContext, mService, e));
290                         } else {
291                             sendErrorResult(Constants.STATUS_INTERNAL_ERROR, t);
292                         }
293                     }
294                 },
295                 mInjector.getExecutor());
296     }
297 
298     @Override
cleanUpServiceParams()299     public void cleanUpServiceParams() {}
300 
sendDisplayResult(SurfacePackage surfacePackage)301     private void sendDisplayResult(SurfacePackage surfacePackage) {
302         if (surfacePackage != null) {
303             sendSuccessResult(surfacePackage);
304         } else {
305             sLogger.w(TAG + ": surfacePackages is null or empty");
306             sendErrorResult(Constants.STATUS_INTERNAL_ERROR, 0);
307         }
308     }
309 
sendSuccessResult(SurfacePackage surfacePackage)310     private void sendSuccessResult(SurfacePackage surfacePackage) {
311         int responseCode = Constants.STATUS_SUCCESS;
312         try {
313             mCallback.onSuccess(
314                     surfacePackage,
315                     new CalleeMetadata.Builder()
316                             .setServiceEntryTimeMillis(mServiceEntryTimeMillis)
317                             .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build());
318         } catch (RemoteException e) {
319             responseCode = Constants.STATUS_INTERNAL_ERROR;
320             sLogger.w(TAG + ": Callback error", e);
321         }
322     }
323 
sendErrorResult(int errorCode, int isolatedServiceErrorCode)324     private void sendErrorResult(int errorCode, int isolatedServiceErrorCode) {
325         try {
326             mCallback.onError(
327                     errorCode,
328                     isolatedServiceErrorCode,
329                     null,
330                     new CalleeMetadata.Builder()
331                             .setServiceEntryTimeMillis(mServiceEntryTimeMillis)
332                             .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build());
333         } catch (RemoteException e) {
334             sLogger.w(TAG + ": Callback error", e);
335         }
336     }
337 
sendErrorResult(int errorCode, Throwable t)338     private void sendErrorResult(int errorCode, Throwable t) {
339         try {
340             mCallback.onError(
341                     errorCode,
342                     0,
343                     DebugUtils.getErrorMessage(mContext, t),
344                     new CalleeMetadata.Builder()
345                             .setServiceEntryTimeMillis(mServiceEntryTimeMillis)
346                             .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build());
347         } catch (RemoteException e) {
348             sLogger.w(TAG + ": Callback error", e);
349         }
350     }
351 }
352