blob: b070e504b8ea3be6f4f684c57965e94896a46fda [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.adservices.service.adselection;
import static android.adservices.common.AdServicesStatusUtils.STATUS_UNKNOWN_ERROR;
import android.adservices.adselection.AdSelectionCallback;
import android.adservices.adselection.AdSelectionFromOutcomesInput;
import android.adservices.adselection.AdSelectionOutcome;
import android.adservices.adselection.AdSelectionResponse;
import android.adservices.common.AdSelectionSignals;
import android.adservices.common.FledgeErrorResponse;
import android.annotation.NonNull;
import android.net.Uri;
import android.os.RemoteException;
import com.android.adservices.LogUtil;
import com.android.adservices.data.adselection.AdSelectionEntryDao;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
/**
* Orchestrator that runs the logic retrieved on a list of outcomes and signals.
*
* <p>Class takes in an executor on which it runs the OutcomeSelection logic
*/
public class OutcomeSelectionRunner {
@NonNull private final AdSelectionEntryDao mAdSelectionEntryDao;
@NonNull protected final ListeningExecutorService mBackgroundExecutorService;
@NonNull private final ListeningExecutorService mLightweightExecutorService;
@VisibleForTesting
static final String AD_OUTCOMES_LIST_INPUT_CANNOT_BE_EMPTY_MSG =
"Ad outcomes list should at least have one element inside";
public OutcomeSelectionRunner(
@NonNull final AdSelectionEntryDao adSelectionEntryDao,
@NonNull final ExecutorService backgroundExecutorService,
@NonNull final ExecutorService lightweightExecutorService) {
Objects.requireNonNull(adSelectionEntryDao);
Objects.requireNonNull(backgroundExecutorService);
Objects.requireNonNull(lightweightExecutorService);
mAdSelectionEntryDao = adSelectionEntryDao;
mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService);
mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService);
}
/**
* Runs outcome selection logic on given list of outcomes and signals.
*
* @param inputParams includes list of outcomes, selection signals and URI to download the logic
* @param callback is used to notify the results to the caller
*/
public void runOutcomeSelection(
@NonNull AdSelectionFromOutcomesInput inputParams,
@NonNull AdSelectionCallback callback) {
Objects.requireNonNull(inputParams);
Objects.requireNonNull(callback);
try {
ListenableFuture<Void> validateRequestFuture =
Futures.submit(() -> validateRequest(inputParams), mLightweightExecutorService);
ListenableFuture<AdSelectionOutcome> adSelectionOutcomeFuture =
FluentFuture.from(validateRequestFuture)
.transformAsync(
ignoredVoid ->
orchestrateOutcomeSelection(
inputParams.getAdOutcomes(),
inputParams.getSelectionSignals(),
inputParams.getSelectionLogicUri()),
mLightweightExecutorService);
Futures.addCallback(
adSelectionOutcomeFuture,
new FutureCallback<AdSelectionOutcome>() {
@Override
public void onSuccess(AdSelectionOutcome result) {
notifySuccessToCaller(result, callback);
}
@Override
public void onFailure(Throwable t) {
notifyFailureToCaller(t, callback);
}
},
mLightweightExecutorService);
} catch (Throwable t) {
LogUtil.v("runOutcomeSelection fails fast with exception %s.", t.toString());
notifyFailureToCaller(t, callback);
}
}
private ListenableFuture<AdSelectionOutcome> orchestrateOutcomeSelection(
@NonNull List<AdSelectionOutcome> adOutcomes,
@NonNull AdSelectionSignals selectionSignals,
@NonNull Uri selectionUri) {
// TODO(249843968): Implement outcome selection service orchestration
FluentFuture<Map<Long, Double>> outcomeIdBidPairsFuture =
FluentFuture.from(retrieveAdSelectionIdToBidMap(adOutcomes));
return mLightweightExecutorService.submit(() -> null);
}
private void notifySuccessToCaller(AdSelectionOutcome result, AdSelectionCallback callback) {
try {
if (result == null) {
callback.onSuccess(null);
} else {
callback.onSuccess(
new AdSelectionResponse.Builder()
.setAdSelectionId(result.getAdSelectionId())
.setRenderUri(result.getRenderUri())
.build());
}
} catch (RemoteException e) {
LogUtil.e(e, "Encountered exception during callback");
throw new IllegalStateException(e);
}
}
private void notifyFailureToCaller(Throwable t, AdSelectionCallback callback) {
// TODO(257678151): Implement more informative failure handling
try {
LogUtil.e("Notify caller of error: " + t);
callback.onFailure(
new FledgeErrorResponse.Builder()
.setErrorMessage(t.getMessage())
.setStatusCode(STATUS_UNKNOWN_ERROR)
.build());
} catch (RemoteException e) {
LogUtil.e("Failed to notify caller: " + e);
}
}
private Void validateRequest(AdSelectionFromOutcomesInput adSelectionOutcome) {
// TODO(258020359): Implement validators for AdSelectionFromOutcomesInput
Preconditions.checkArgument(
!adSelectionOutcome.getAdOutcomes().isEmpty(),
new IllegalArgumentException(AD_OUTCOMES_LIST_INPUT_CANNOT_BE_EMPTY_MSG));
return null;
}
/** Retrieves winner ad bids using ad selection ids of already run ad selections' outcomes. */
@VisibleForTesting
ListenableFuture<Map<Long, Double>> retrieveAdSelectionIdToBidMap(
List<AdSelectionOutcome> adOutcomes) {
Map<Long, Double> retrievedIdBidPairs = new HashMap<>();
return mBackgroundExecutorService.submit(
() -> {
List<Long> adOutcomeIds =
adOutcomes.parallelStream()
.map(AdSelectionOutcome::getAdSelectionId)
.collect(Collectors.toList());
mAdSelectionEntryDao.getAdSelectionEntities(adOutcomeIds).parallelStream()
.forEach(
e ->
retrievedIdBidPairs.put(
e.getAdSelectionId(), e.getWinningAdBid()));
return retrievedIdBidPairs;
});
}
}