• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.WorkerThread;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.os.PersistableBundle;
27 import android.os.RemoteException;
28 
29 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
30 
31 import java.util.Objects;
32 import java.util.concurrent.ArrayBlockingQueue;
33 import java.util.concurrent.BlockingQueue;
34 
35 /**
36  * Generates event tracking URLs for a request. The service can embed these URLs within the
37  * HTML output as needed. When the HTML is rendered within an ODP WebView, ODP will intercept
38  * requests to these URLs, call
39  * {@code IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}, and log the returned
40  * output in the EVENTS table.
41  *
42  */
43 public class EventUrlProvider {
44     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
45     private static final String TAG = EventUrlProvider.class.getSimpleName();
46     private static final long ASYNC_TIMEOUT_MS = 1000;
47 
48     @NonNull private final IDataAccessService mDataAccessService;
49 
50     /** @hide */
EventUrlProvider(@onNull IDataAccessService binder)51     public EventUrlProvider(@NonNull IDataAccessService binder) {
52         mDataAccessService = Objects.requireNonNull(binder);
53     }
54 
55     /**
56      * Creates an event tracking URL that returns the provided response. Returns HTTP Status
57      * 200 (OK) if the response data is not empty. Returns HTTP Status 204 (No Content) if the
58      * response data is empty.
59      *
60      * @param eventParams The data to be passed to
61      *     {@code IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}
62      *     when the event occurs.
63      * @param responseData The content to be returned to the WebView when the URL is fetched.
64      * @param mimeType The Mime Type of the URL response.
65      * @return An ODP event URL that can be inserted into a WebView.
66      */
67     @WorkerThread
createEventTrackingUrlWithResponse( @onNull PersistableBundle eventParams, @Nullable byte[] responseData, @Nullable String mimeType)68     @NonNull public Uri createEventTrackingUrlWithResponse(
69             @NonNull PersistableBundle eventParams,
70             @Nullable byte[] responseData,
71             @Nullable String mimeType) {
72         final long startTimeMillis = System.currentTimeMillis();
73         Bundle params = new Bundle();
74         params.putParcelable(Constants.EXTRA_EVENT_PARAMS, eventParams);
75         params.putByteArray(Constants.EXTRA_RESPONSE_DATA, responseData);
76         params.putString(Constants.EXTRA_MIME_TYPE, mimeType);
77         return getUrl(params, Constants.API_NAME_EVENT_URL_CREATE_WITH_RESPONSE, startTimeMillis);
78     }
79 
80     /**
81      * Creates an event tracking URL that redirects to the provided destination URL when it is
82      * clicked in an ODP webview.
83      *
84      * @param eventParams The data to be passed to
85      *     {@code IsolatedWorker#onEvent(EventInput, android.os.OutcomeReceiver)}
86      *     when the event occurs
87      * @param destinationUrl The URL to redirect to.
88      * @return An ODP event URL that can be inserted into a WebView.
89      */
90     @WorkerThread
createEventTrackingUrlWithRedirect( @onNull PersistableBundle eventParams, @Nullable Uri destinationUrl)91     @NonNull public Uri createEventTrackingUrlWithRedirect(
92             @NonNull PersistableBundle eventParams,
93             @Nullable Uri destinationUrl) {
94         final long startTimeMillis = System.currentTimeMillis();
95         Bundle params = new Bundle();
96         params.putParcelable(Constants.EXTRA_EVENT_PARAMS, eventParams);
97         params.putString(Constants.EXTRA_DESTINATION_URL, destinationUrl.toString());
98         return getUrl(params, Constants.API_NAME_EVENT_URL_CREATE_WITH_REDIRECT, startTimeMillis);
99     }
100 
getUrl( @onNull Bundle params, int apiName, long startTimeMillis)101     @NonNull private Uri getUrl(
102             @NonNull Bundle params, int apiName, long startTimeMillis) {
103         int responseCode = Constants.STATUS_SUCCESS;
104         try {
105             BlockingQueue<CallbackResult> asyncResult = new ArrayBlockingQueue<>(1);
106 
107             mDataAccessService.onRequest(
108                     Constants.DATA_ACCESS_OP_GET_EVENT_URL,
109                     params,
110                     new IDataAccessServiceCallback.Stub() {
111                         @Override
112                         public void onSuccess(@NonNull Bundle result) {
113                             asyncResult.add(new CallbackResult(result, 0));
114                         }
115                         @Override
116                         public void onError(int errorCode) {
117                             asyncResult.add(new CallbackResult(null, errorCode));
118                         }
119                 });
120             CallbackResult callbackResult = asyncResult.take();
121             Objects.requireNonNull(callbackResult);
122             if (callbackResult.mErrorCode != 0) {
123                 throw new IllegalStateException("Error: " + callbackResult.mErrorCode);
124             }
125             Bundle result = Objects.requireNonNull(callbackResult.mResult);
126             Uri url = Objects.requireNonNull(
127                     result.getParcelable(Constants.EXTRA_RESULT, Uri.class));
128             return url;
129         } catch (InterruptedException | RemoteException e) {
130             responseCode = Constants.STATUS_INTERNAL_ERROR;
131             throw new RuntimeException(e);
132         } finally {
133             try {
134                 mDataAccessService.logApiCallStats(
135                         apiName,
136                         System.currentTimeMillis() - startTimeMillis,
137                         responseCode);
138             } catch (Exception e) {
139                 sLogger.d(e, TAG + ": failed to log metrics");
140             }
141         }
142     }
143 
144     private static class CallbackResult {
145         final Bundle mResult;
146         final int mErrorCode;
147 
CallbackResult(Bundle result, int errorCode)148         CallbackResult(Bundle result, int errorCode) {
149             mResult = result;
150             mErrorCode = errorCode;
151         }
152     }
153 }
154