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