// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/attribution_reporting/attribution_os_level_manager_android.h"
#include <stddef.h>
#include <iterator>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/android/jni_string.h"
#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/sequence_checker.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "components/attribution_reporting/os_registration.h"
#include "components/attribution_reporting/registrar.h"
#include "content/browser/attribution_reporting/attribution_input_event.h"
#include "content/browser/attribution_reporting/attribution_manager.h"
#include "content/browser/attribution_reporting/attribution_os_level_manager.h"
#include "content/browser/attribution_reporting/attribution_reporting.mojom.h"
#include "content/browser/attribution_reporting/os_registration.h"
#include "content/browser/browser_thread_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/content_browser_client.h"
#include "services/network/public/cpp/attribution_utils.h"
#include "url/android/gurl_android.h"
#include "url/gurl.h"
#include "url/origin.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "content/public/android/content_jni_headers/AttributionOsLevelManager_jni.h"
using jni_zero::AttachCurrentThread;
using jni_zero::ScopedJavaLocalRef;
namespace content {
namespace {
using ::attribution_reporting::Registrar;
using ApiState = ContentBrowserClient::AttributionReportingOsApiState;
int GetDeletionMode(bool delete_rate_limit_data) {
// See
// https://developer.android.com/reference/androidx/privacysandbox/ads/adservices/measurement/DeletionRequest#constants
// for constant values.
static constexpr int kDeletionModeAll = 0;
static constexpr int kDeletionModeExcludeInternalData = 1;
return delete_rate_limit_data ? kDeletionModeAll
: kDeletionModeExcludeInternalData;
}
int GetMatchBehavior(BrowsingDataFilterBuilder::Mode mode) {
// See
// https://developer.android.com/reference/androidx/privacysandbox/ads/adservices/measurement/DeletionRequest#constants
// for constant values.
static constexpr int kMatchBehaviorDelete = 0;
static constexpr int kMatchBehaviorPreserve = 1;
switch (mode) {
case BrowsingDataFilterBuilder::Mode::kDelete:
return kMatchBehaviorDelete;
case BrowsingDataFilterBuilder::Mode::kPreserve:
return kMatchBehaviorPreserve;
}
}
ApiState ConvertToApiState(int value) {
// See
// https://developer.android.com/reference/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager
// for constant values.
static constexpr int kMeasurementApiStateDisabled = 0;
static constexpr int kMeasurementApiStateEnabled = 1;
switch (value) {
case kMeasurementApiStateDisabled:
return ApiState::kDisabled;
case kMeasurementApiStateEnabled:
return ApiState::kEnabled;
default:
return ApiState::kDisabled;
}
}
void GetMeasurementApiStatus() {
base::ElapsedThreadTimer timer;
Java_AttributionOsLevelManager_getMeasurementApiStatus(AttachCurrentThread());
if (timer.is_supported()) {
base::UmaHistogramTimes("Conversions.GetMeasurementStatusTime",
timer.Elapsed());
}
}
} // namespace
static void JNI_AttributionOsLevelManager_OnMeasurementStateReturned(
JNIEnv* env,
jint state) {
ApiState api_state = ConvertToApiState(state);
if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
AttributionOsLevelManager::SetApiState(api_state);
return;
}
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&AttributionOsLevelManager::SetApiState, api_state));
}
AttributionOsLevelManagerAndroid::AttributionOsLevelManagerAndroid() {
jobj_ = Java_AttributionOsLevelManager_Constructor(
AttachCurrentThread(), reinterpret_cast<intptr_t>(this));
if (AttributionOsLevelManager::ShouldInitializeApiState()) {
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})
->PostTask(FROM_HERE, base::BindOnce(&GetMeasurementApiStatus));
}
}
AttributionOsLevelManagerAndroid::~AttributionOsLevelManagerAndroid() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Java_AttributionOsLevelManager_nativeDestroyed(AttachCurrentThread(), jobj_);
}
namespace {
// 3/4 of the Android API calls below have atomic success/failure, while the
// fourth has success/failure per item.
std::vector<bool> AtomicSuccess(size_t num_items, bool success) {
return std::vector<bool>(num_items, success);
}
void MergeIndividualSuccessAndInvokeCallback(
std::vector<bool>& successes,
size_t& remaining,
base::OnceCallback<void(const std::vector<bool>&)>& callback,
size_t i,
bool success) {
CHECK_GT(remaining, 0u);
--remaining;
successes.at(i) = success;
if (remaining == 0) {
std::move(callback).Run(successes);
}
}
} // namespace
void AttributionOsLevelManagerAndroid::Register(
OsRegistration registration,
const std::vector<bool>& is_debug_key_allowed,
RegisterCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const size_t num_items = registration.registration_items.size();
CHECK_EQ(num_items, is_debug_key_allowed.size());
JNIEnv* env = AttachCurrentThread();
Registrar registrar = registration.registrar;
attribution_reporting::mojom::RegistrationType type = registration.GetType();
std::vector<ScopedJavaLocalRef<jobject>> registration_urls;
base::ranges::transform(
registration.registration_items, std::back_inserter(registration_urls),
[env](const attribution_reporting::OsRegistrationItem& item) {
return url::GURLAndroid::FromNativeGURL(env, item.url);
});
auto top_level_origin = url::GURLAndroid::FromNativeGURL(
env, registration.top_level_origin.GetURL());
std::optional<AttributionInputEvent> input_event = registration.input_event;
auto bound_callback =
base::BindOnce(std::move(callback), std::move(registration));
switch (type) {
case attribution_reporting::mojom::RegistrationType::kSource: {
DCHECK(input_event.has_value());
int request_id = next_callback_id_++;
pending_registration_callbacks_.emplace(
request_id, base::BindOnce(&AtomicSuccess, num_items)
.Then(std::move(bound_callback)));
switch (registrar) {
case Registrar::kWeb: {
auto sources =
Java_AttributionOsLevelManager_createWebSourceParamsList(
env, num_items);
for (size_t i = 0; i < num_items; ++i) {
Java_AttributionOsLevelManager_addWebSourceParams(
env, sources, registration_urls[i], is_debug_key_allowed[i]);
}
Java_AttributionOsLevelManager_registerWebAttributionSource(
env, jobj_, request_id, sources, top_level_origin,
input_event->input_event);
break;
}
case Registrar::kOs: {
Java_AttributionOsLevelManager_registerAttributionSource(
env, jobj_, request_id, registration_urls,
input_event->input_event);
break;
}
}
break;
}
case attribution_reporting::mojom::RegistrationType::kTrigger: {
switch (registrar) {
case Registrar::kWeb: {
int request_id = next_callback_id_++;
pending_registration_callbacks_.emplace(
request_id, base::BindOnce(&AtomicSuccess, num_items)
.Then(std::move(bound_callback)));
auto triggers =
Java_AttributionOsLevelManager_createWebTriggerParamsList(
env, num_items);
for (size_t i = 0; i < num_items; ++i) {
Java_AttributionOsLevelManager_addWebTriggerParams(
env, triggers, registration_urls[i], is_debug_key_allowed[i]);
}
Java_AttributionOsLevelManager_registerWebAttributionTrigger(
env, jobj_, request_id, triggers, top_level_origin);
break;
}
case Registrar::kOs: {
auto merge_results =
base::BindRepeating(&MergeIndividualSuccessAndInvokeCallback,
base::OwnedRef(std::vector<bool>(num_items)),
base::OwnedRef(num_items),
base::OwnedRef(std::move(bound_callback)));
for (size_t i = 0; const auto& registration_url : registration_urls) {
int request_id = next_callback_id_++;
pending_registration_callbacks_.emplace(
request_id, base::BindOnce(merge_results, i));
++i;
Java_AttributionOsLevelManager_registerAttributionTrigger(
env, jobj_, request_id, registration_url);
}
break;
}
}
break;
}
}
}
void AttributionOsLevelManagerAndroid::ClearData(
base::Time delete_begin,
base::Time delete_end,
const std::set<url::Origin>& origins,
const std::set<std::string>& domains,
BrowsingDataFilterBuilder::Mode mode,
bool delete_rate_limit_data,
base::OnceClosure done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
JNIEnv* env = AttachCurrentThread();
std::vector<ScopedJavaLocalRef<jobject>> j_origins;
base::ranges::transform(
origins, std::back_inserter(j_origins), [env](const url::Origin& origin) {
return url::GURLAndroid::FromNativeGURL(env, origin.GetURL());
});
int request_id = next_callback_id_++;
pending_data_deletion_callbacks_.emplace(request_id, std::move(done));
Java_AttributionOsLevelManager_deleteRegistrations(
env, jobj_, request_id, delete_begin.InMillisecondsSinceUnixEpoch(),
delete_end.InMillisecondsSinceUnixEpoch(), j_origins,
std::vector<std::string>(domains.begin(), domains.end()),
GetDeletionMode(delete_rate_limit_data), GetMatchBehavior(mode));
}
void AttributionOsLevelManagerAndroid::OnRegistrationCompleted(JNIEnv* env,
jint request_id,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = pending_registration_callbacks_.find(request_id);
if (it == pending_registration_callbacks_.end()) {
return;
}
std::move(it->second).Run(success);
pending_registration_callbacks_.erase(it);
}
void AttributionOsLevelManagerAndroid::OnDataDeletionCompleted(
JNIEnv* env,
jint request_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = pending_data_deletion_callbacks_.find(request_id);
if (it == pending_data_deletion_callbacks_.end()) {
return;
}
std::move(it->second).Run();
pending_data_deletion_callbacks_.erase(it);
}
} // namespace content