1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/permissions/prediction_based_permission_ui_selector.h"
6
7 #include "base/command_line.h"
8 #include "base/feature_list.h"
9 #include "base/time/default_clock.h"
10 #include "base/util/values/values_util.h"
11 #include "chrome/browser/permissions/prediction_service_factory.h"
12 #include "chrome/browser/permissions/prediction_service_request.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/common/chrome_features.h"
15 #include "chrome/common/chrome_switches.h"
16 #include "chrome/common/pref_names.h"
17 #include "components/permissions/permission_util.h"
18 #include "components/permissions/prediction_service/prediction_service.h"
19 #include "components/permissions/prediction_service/prediction_service_messages.pb.h"
20 #include "components/prefs/pref_service.h"
21 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
22
23 namespace {
24
25 using QuietUiReason = PredictionBasedPermissionUiSelector::QuietUiReason;
26 using Decision = PredictionBasedPermissionUiSelector::Decision;
27
28 constexpr auto VeryUnlikely = permissions::
29 PermissionSuggestion_Likelihood_DiscretizedLikelihood_VERY_UNLIKELY;
30
31 // The data we consider can only be at most 28 days old to match the data that
32 // the ML model is built on.
33 constexpr base::TimeDelta kPermissionActionCutoffAge =
34 base::TimeDelta::FromDays(28);
35
36 constexpr char kPermissionActionEntryActionKey[] = "action";
37 constexpr char kPermissionActionEntryTimestampKey[] = "time";
38
39 base::Optional<
40 permissions::PermissionSuggestion_Likelihood_DiscretizedLikelihood>
ParsePredictionServiceMockLikelihood(const std::string & value)41 ParsePredictionServiceMockLikelihood(const std::string& value) {
42 if (value == "very-unlikely") {
43 return permissions::
44 PermissionSuggestion_Likelihood_DiscretizedLikelihood_VERY_UNLIKELY;
45 } else if (value == "unlikely") {
46 return permissions::
47 PermissionSuggestion_Likelihood_DiscretizedLikelihood_UNLIKELY;
48 } else if (value == "neutral") {
49 return permissions::
50 PermissionSuggestion_Likelihood_DiscretizedLikelihood_NEUTRAL;
51 } else if (value == "likely") {
52 return permissions::
53 PermissionSuggestion_Likelihood_DiscretizedLikelihood_LIKELY;
54 } else if (value == "very-likely") {
55 return permissions::
56 PermissionSuggestion_Likelihood_DiscretizedLikelihood_VERY_LIKELY;
57 }
58
59 return base::nullopt;
60 }
61
ShouldPredictionTriggerQuietUi(permissions::PermissionUmaUtil::PredictionGrantLikelihood likelihood)62 bool ShouldPredictionTriggerQuietUi(
63 permissions::PermissionUmaUtil::PredictionGrantLikelihood likelihood) {
64 return likelihood == VeryUnlikely;
65 }
66
67 } // namespace
68
PredictionBasedPermissionUiSelector(Profile * profile)69 PredictionBasedPermissionUiSelector::PredictionBasedPermissionUiSelector(
70 Profile* profile)
71 : profile_(profile) {
72 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
73 switches::kPredictionServiceMockLikelihood)) {
74 auto mock_likelihood = ParsePredictionServiceMockLikelihood(
75 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
76 switches::kPredictionServiceMockLikelihood));
77 if (mock_likelihood.has_value())
78 set_likelihood_override(mock_likelihood.value());
79 }
80 }
81
82 PredictionBasedPermissionUiSelector::~PredictionBasedPermissionUiSelector() =
83 default;
84
SelectUiToUse(permissions::PermissionRequest * request,DecisionMadeCallback callback)85 void PredictionBasedPermissionUiSelector::SelectUiToUse(
86 permissions::PermissionRequest* request,
87 DecisionMadeCallback callback) {
88 if (!IsAllowedToUseAssistedPrompts()) {
89 std::move(callback).Run(Decision::UseNormalUiAndShowNoWarning());
90 return;
91 }
92
93 if (likelihood_override_for_testing_.has_value()) {
94 if (ShouldPredictionTriggerQuietUi(
95 likelihood_override_for_testing_.value())) {
96 std::move(callback).Run(
97 Decision(QuietUiReason::kPredictedVeryUnlikelyGrant,
98 Decision::ShowNoWarning()));
99 } else {
100 std::move(callback).Run(Decision::UseNormalUiAndShowNoWarning());
101 }
102 return;
103 }
104
105 last_request_grant_likelihood_ = base::nullopt;
106
107 DCHECK(!request_);
108 permissions::PredictionService* service =
109 PredictionServiceFactory::GetForProfile(profile_);
110 callback_ = std::move(callback);
111 request_ = std::make_unique<PredictionServiceRequest>(
112 service, BuildPredictionRequestFeatures(request),
113 base::BindOnce(
114 &PredictionBasedPermissionUiSelector::LookupReponseReceived,
115 base::Unretained(this)));
116 }
117
Cancel()118 void PredictionBasedPermissionUiSelector::Cancel() {
119 request_.reset();
120 callback_.Reset();
121 }
122
123 base::Optional<permissions::PermissionUmaUtil::PredictionGrantLikelihood>
PredictedGrantLikelihoodForUKM()124 PredictionBasedPermissionUiSelector::PredictedGrantLikelihoodForUKM() {
125 return last_request_grant_likelihood_;
126 }
127
128 permissions::PredictionRequestFeatures
BuildPredictionRequestFeatures(permissions::PermissionRequest * request)129 PredictionBasedPermissionUiSelector::BuildPredictionRequestFeatures(
130 permissions::PermissionRequest* request) {
131 permissions::PredictionRequestFeatures features;
132 features.gesture = request->GetGestureType();
133 features.type = request->GetPermissionRequestType();
134 auto* permission_actions =
135 profile_->GetPrefs()->GetList(prefs::kNotificationPermissionActions);
136
137 base::Time cutoff = base::Time::Now() - kPermissionActionCutoffAge;
138 for (const auto& action : *permission_actions) {
139 const base::Optional<base::Time> timestamp =
140 util::ValueToTime(action.FindKey(kPermissionActionEntryTimestampKey));
141
142 if (!timestamp || *timestamp < cutoff)
143 continue;
144
145 const base::Optional<int> past_action_as_int =
146 action.FindIntKey(kPermissionActionEntryActionKey);
147 DCHECK(past_action_as_int);
148
149 const permissions::PermissionAction past_action =
150 static_cast<permissions::PermissionAction>(*past_action_as_int);
151
152 // TODO(andypaicu): implement recording all prompts outcomes regardless of
153 // type. We currently only count notification prompts.
154 switch (past_action) {
155 case permissions::PermissionAction::DENIED:
156 features.requested_permission_counts.denies++;
157 features.all_permission_counts.denies++;
158 break;
159 case permissions::PermissionAction::GRANTED:
160 features.requested_permission_counts.grants++;
161 features.all_permission_counts.grants++;
162 break;
163 case permissions::PermissionAction::DISMISSED:
164 features.requested_permission_counts.dismissals++;
165 features.all_permission_counts.dismissals++;
166 break;
167 case permissions::PermissionAction::IGNORED:
168 features.requested_permission_counts.ignores++;
169 features.all_permission_counts.ignores++;
170 break;
171 default:
172 // Anything else is ignored.
173 break;
174 }
175 }
176
177 return features;
178 }
179
LookupReponseReceived(bool lookup_succesful,bool response_from_cache,std::unique_ptr<permissions::GetSuggestionsResponse> response)180 void PredictionBasedPermissionUiSelector::LookupReponseReceived(
181 bool lookup_succesful,
182 bool response_from_cache,
183 std::unique_ptr<permissions::GetSuggestionsResponse> response) {
184 request_.reset();
185 if (!lookup_succesful || !response || response->suggestion_size() == 0) {
186 std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
187 return;
188 }
189
190 last_request_grant_likelihood_ =
191 response->suggestion(0).grant_likelihood().discretized_likelihood();
192
193 if (ShouldPredictionTriggerQuietUi(last_request_grant_likelihood_.value())) {
194 std::move(callback_).Run(Decision(
195 QuietUiReason::kPredictedVeryUnlikelyGrant, Decision::ShowNoWarning()));
196 return;
197 }
198
199 std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
200 }
201
IsAllowedToUseAssistedPrompts()202 bool PredictionBasedPermissionUiSelector::IsAllowedToUseAssistedPrompts() {
203 // We need to also check `kQuietNotificationPrompts` here since there is no
204 // generic safeguard anywhere else in the stack.
205 return base::FeatureList::IsEnabled(features::kQuietNotificationPrompts) &&
206 base::FeatureList::IsEnabled(features::kPermissionPredictions) &&
207 safe_browsing::IsEnhancedProtectionEnabled(*(profile_->GetPrefs()));
208 }
209