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