blob: 417393ee03ab1e69c7f8d484b3f4d7f0542ca832 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/permissions/prediction_based_permission_ui_selector.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/time/default_clock.h"
#include "chrome/browser/permissions/permission_actions_history.h"
#include "chrome/browser/permissions/prediction_service_factory.h"
#include "chrome/browser/permissions/prediction_service_request.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/prediction_service/prediction_service.h"
#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
namespace {
using QuietUiReason = PredictionBasedPermissionUiSelector::QuietUiReason;
using Decision = PredictionBasedPermissionUiSelector::Decision;
constexpr auto VeryUnlikely = permissions::
PermissionPrediction_Likelihood_DiscretizedLikelihood_VERY_UNLIKELY;
// The data we consider can only be at most 28 days old to match the data that
// the ML model is built on.
constexpr base::TimeDelta kPermissionActionCutoffAge = base::Days(28);
// Only send requests if there are at least 4 action in the user's history for
// the particular permission type.
constexpr size_t kRequestedPermissionMinimumHistoricalActions = 4;
absl::optional<
permissions::PermissionPrediction_Likelihood_DiscretizedLikelihood>
ParsePredictionServiceMockLikelihood(const std::string& value) {
if (value == "very-unlikely") {
return permissions::
PermissionPrediction_Likelihood_DiscretizedLikelihood_VERY_UNLIKELY;
} else if (value == "unlikely") {
return permissions::
PermissionPrediction_Likelihood_DiscretizedLikelihood_UNLIKELY;
} else if (value == "neutral") {
return permissions::
PermissionPrediction_Likelihood_DiscretizedLikelihood_NEUTRAL;
} else if (value == "likely") {
return permissions::
PermissionPrediction_Likelihood_DiscretizedLikelihood_LIKELY;
} else if (value == "very-likely") {
return permissions::
PermissionPrediction_Likelihood_DiscretizedLikelihood_VERY_LIKELY;
}
return absl::nullopt;
}
bool ShouldPredictionTriggerQuietUi(
permissions::PermissionUmaUtil::PredictionGrantLikelihood likelihood) {
return likelihood == VeryUnlikely;
}
} // namespace
PredictionBasedPermissionUiSelector::PredictionBasedPermissionUiSelector(
Profile* profile)
: profile_(profile) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kPredictionServiceMockLikelihood)) {
auto mock_likelihood = ParsePredictionServiceMockLikelihood(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kPredictionServiceMockLikelihood));
if (mock_likelihood.has_value())
set_likelihood_override(mock_likelihood.value());
}
}
PredictionBasedPermissionUiSelector::~PredictionBasedPermissionUiSelector() =
default;
void PredictionBasedPermissionUiSelector::SelectUiToUse(
permissions::PermissionRequest* request,
DecisionMadeCallback callback) {
VLOG(1) << "[CPSS] Selector activated";
callback_ = std::move(callback);
last_request_grant_likelihood_ = absl::nullopt;
if (!IsAllowedToUseAssistedPrompts(request->request_type())) {
VLOG(1) << "[CPSS] Configuration either does not allows CPSS requests or "
"the request was held back";
std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
return;
}
auto features = BuildPredictionRequestFeatures(request);
if (features.requested_permission_counts.total() <
kRequestedPermissionMinimumHistoricalActions) {
VLOG(1) << "[CPSS] Historic prompt count ("
<< features.requested_permission_counts.total()
<< ") is smaller than threshold ("
<< kRequestedPermissionMinimumHistoricalActions << ")";
std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
return;
}
if (likelihood_override_for_testing_.has_value()) {
VLOG(1) << "[CPSS] Using likelihood override value that was provided via "
"command line";
if (ShouldPredictionTriggerQuietUi(
likelihood_override_for_testing_.value())) {
std::move(callback_).Run(
Decision(QuietUiReason::kPredictedVeryUnlikelyGrant,
Decision::ShowNoWarning()));
} else {
std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
}
return;
}
DCHECK(!request_);
permissions::PredictionService* service =
PredictionServiceFactory::GetForProfile(profile_);
VLOG(1) << "[CPSS] Starting prediction service request";
request_ = std::make_unique<PredictionServiceRequest>(
service, features,
base::BindOnce(
&PredictionBasedPermissionUiSelector::LookupReponseReceived,
base::Unretained(this)));
}
void PredictionBasedPermissionUiSelector::Cancel() {
request_.reset();
callback_.Reset();
}
bool PredictionBasedPermissionUiSelector::IsPermissionRequestSupported(
permissions::RequestType request_type) {
return request_type == permissions::RequestType::kNotifications ||
request_type == permissions::RequestType::kGeolocation;
}
absl::optional<permissions::PermissionUmaUtil::PredictionGrantLikelihood>
PredictionBasedPermissionUiSelector::PredictedGrantLikelihoodForUKM() {
return last_request_grant_likelihood_;
}
permissions::PredictionRequestFeatures
PredictionBasedPermissionUiSelector::BuildPredictionRequestFeatures(
permissions::PermissionRequest* request) {
permissions::PredictionRequestFeatures features;
features.gesture = request->GetGestureType();
features.type = request->request_type();
base::Time cutoff = base::Time::Now() - kPermissionActionCutoffAge;
auto* action_history = PermissionActionsHistory::GetForProfile(profile_);
auto actions = action_history->GetHistory(cutoff, request->request_type());
FillInActionCounts(&features.requested_permission_counts, actions);
actions = action_history->GetHistory(cutoff);
FillInActionCounts(&features.all_permission_counts, actions);
return features;
}
void PredictionBasedPermissionUiSelector::LookupReponseReceived(
bool lookup_succesful,
bool response_from_cache,
std::unique_ptr<permissions::GeneratePredictionsResponse> response) {
request_.reset();
if (!lookup_succesful || !response || response->prediction_size() == 0) {
VLOG(1) << "[CPSS] Prediction service request failed";
std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
return;
}
last_request_grant_likelihood_ =
response->prediction(0).grant_likelihood().discretized_likelihood();
VLOG(1)
<< "[CPSS] Prediction service request succeeded and received likelihood: "
<< last_request_grant_likelihood_.value();
if (ShouldPredictionTriggerQuietUi(last_request_grant_likelihood_.value())) {
std::move(callback_).Run(Decision(
QuietUiReason::kPredictedVeryUnlikelyGrant, Decision::ShowNoWarning()));
return;
}
std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
}
bool PredictionBasedPermissionUiSelector::IsAllowedToUseAssistedPrompts(
permissions::RequestType request_type) {
// We need to also check `kQuietNotificationPrompts` here since there is no
// generic safeguard anywhere else in the stack.
if (!base::FeatureList::IsEnabled(features::kQuietNotificationPrompts) ||
!safe_browsing::IsSafeBrowsingEnabled(*(profile_->GetPrefs()))) {
return false;
}
double hold_back_chance = 0.0;
bool is_permissions_predictions_enabled = false;
switch (request_type) {
case permissions::RequestType::kNotifications:
is_permissions_predictions_enabled =
base::FeatureList::IsEnabled(features::kPermissionPredictions);
hold_back_chance = features::kPermissionPredictionsHoldbackChance.Get();
break;
case permissions::RequestType::kGeolocation:
// Only quiet chip ui is supported for Geolocation
is_permissions_predictions_enabled =
base::FeatureList::IsEnabled(
features::kPermissionGeolocationPredictions) &&
base::FeatureList::IsEnabled(
permissions::features::kPermissionQuietChip);
hold_back_chance =
features::kPermissionGeolocationPredictionsHoldbackChance.Get();
break;
default:
NOTREACHED();
}
if (!is_permissions_predictions_enabled)
return false;
const bool should_hold_back =
hold_back_chance && base::RandDouble() < hold_back_chance;
// Only recording the hold back UMA histogram if the request was actually
// eligible for an assisted prompt
switch (request_type) {
case permissions::RequestType::kNotifications:
base::UmaHistogramBoolean("Permissions.PredictionService.Request",
!should_hold_back);
break;
case permissions::RequestType::kGeolocation:
base::UmaHistogramBoolean(
"Permissions.PredictionService.GeolocationRequest",
!should_hold_back);
break;
default:
NOTREACHED();
}
return !should_hold_back;
}
// static
void PredictionBasedPermissionUiSelector::FillInActionCounts(
permissions::PredictionRequestFeatures::ActionCounts* counts,
const std::vector<PermissionActionsHistory::Entry>& actions) {
for (const auto& entry : actions) {
switch (entry.action) {
case permissions::PermissionAction::DENIED:
counts->denies++;
break;
case permissions::PermissionAction::GRANTED:
counts->grants++;
break;
case permissions::PermissionAction::DISMISSED:
counts->dismissals++;
break;
case permissions::PermissionAction::IGNORED:
counts->ignores++;
break;
default:
// Anything else is ignored.
break;
}
}
}